<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>prometheus</title><link>https://new.halfrost.com/zh/</link><description>Recent content on prometheus</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><copyright>halfrost</copyright><lastBuildDate>Sat, 21 Dec 2019 11:54:00 +0000</lastBuildDate><atom:link href="https://new.halfrost.com/zh/index.xml" rel="self" type="application/rss+xml"/><item><title>Algorithm in LeetCode —— Segment Tree</title><link>https://new.halfrost.com/zh/segment_tree/</link><pubDate>Sat, 21 Dec 2019 11:54:00 +0000</pubDate><guid>https://new.halfrost.com/zh/segment_tree/</guid><description>Segment Tree 的 Tips: 线段数的经典数组实现写法。将合并两个节点 pushUp 逻辑抽象出来了，可以实现任意操作(常见的操作有：加法，取 max，min 等等)。第 218 题，第 303 题，第 307 题，第 699 题。 计数线段树的经典写法。第 315 题，第 327 题，第 493 题。 线段树的树的实现写法。第 715 题，第 732 题。 区间懒惰更新。第 218 题，第 699 题。 离散化。离散化需要注意一个特殊情况：假如三个区间为 [1,10] [1,4] [6,10]，离散化后 x[1]=1,x[2]=4,x[3]=6,x[4]=10。第一个区间为 [1,4]，第二个区间为 [1,2]，第三个区间为 [3,4]，这样一来，区间一 = 区间二 + 区间三，这和离散前的模型不符，离散前，很明显，区间一 &amp;gt; 区间二 + 区间三。正确的做法是：在相差大于 1 的数间加一个数，例如在上面 1 4 6 10 中间加 5，即可 x[1]=1,x[2]=4,x[3]=5,x[4]=6,x[5]=10。这样处理之后，区间一是 1-5 ，区间二是 1-2 ，区间三是 4-5 。 灵活构建线段树。线段树节点可以存储多条信息，合并两个节点的 pushUp 操作也可以是多样的。第 850 题，第 1157 题。 线段树题型从简单到困难:</description></item><item><title>Algorithm in LeetCode —— Sliding Window</title><link>https://new.halfrost.com/zh/sliding_window/</link><pubDate>Sat, 14 Dec 2019 11:46:00 +0000</pubDate><guid>https://new.halfrost.com/zh/sliding_window/</guid><description>Sliding Window 的 Tips: 双指针滑动窗口的经典写法。右指针不断往右移，移动到不能往右移动为止(具体条件根据题目而定)。当右指针到最右边以后，开始挪动左指针，释放窗口左边界。第 3 题，第 76 题，第 209 题，第 424 题，第 438 题，第 567 题，第 713 题，第 763 题，第 845 题，第 881 题，第 904 题，第 978 题，第 992 题，第 1004 题，第 1040 题，第 1052 题。 left, right := 0, -1 for left &amp;lt; len(s) { if right+1 &amp;lt; len(s) &amp;amp;&amp;amp; freq[s[right+1]-&amp;#39;a&amp;#39;] == 0 { freq[s[right+1]-&amp;#39;a&amp;#39;]++ right++ } else { freq[s[left]-&amp;#39;a&amp;#39;]-- left++ } result = max(result, right-left+1) } 滑动窗口经典题。第 239 题，第 480 题。 Title Solution Difficulty Time Space 收藏 3.</description></item><item><title>Algorithm in LeetCode —— Union Find</title><link>https://new.halfrost.com/zh/union_find/</link><pubDate>Sat, 16 Nov 2019 08:31:00 +0000</pubDate><guid>https://new.halfrost.com/zh/union_find/</guid><description>Union Find 的 Tips: 灵活使用并查集的思想，熟练掌握并查集的模板，模板中有两种并查集的实现方式，一种是路径压缩 + 秩优化的版本，另外一种是计算每个集合中元素的个数 + 最大集合元素个数的版本，这两种版本都有各自使用的地方。能使用第一类并查集模板的题目有：第 128 题，第 130 题，第 547 题，第 684 题，第 721 题，第 765 题，第 778 题，第 839 题，第 924 题，第 928 题，第 947 题，第 952 题，第 959 题，第 990 题。能使用第二类并查集模板的题目有：第 803 题，第 952 题。第 803 题秩优化和统计集合个数这些地方会卡时间，如果不优化，会 TLE。 并查集是一种思想，有些题需要灵活使用这种思想，而不是死套模板，如第 399 题，这一题是 stringUnionFind，利用并查集思想实现的。这里每个节点是基于字符串和 map 的，而不是单纯的用 int 节点编号实现的。 有些题死套模板反而做不出来，比如第 685 题，这一题不能路径压缩和秩优化，因为题目中涉及到有向图，需要知道节点的前驱节点，如果路径压缩了，这一题就没法做了。这一题不需要路径压缩和秩优化。 灵活的抽象题目给的信息，将给定的信息合理的编号，使用并查集解题，并用 map 降低时间复杂度，如第 721 题，第 959 题。 关于地图，砖块，网格的题目，可以新建一个特殊节点，将四周边缘的砖块或者网格都 union() 到这个特殊节点上。第 130 题，第 803 题。 能用并查集的题目，一般也可以用 DFS 和 BFS 解答，只不过时间复杂度会高一点。 Title Solution Difficulty Time Space 收藏 128.</description></item><item><title>Algorithm in LeetCode —— Bit Manipulation</title><link>https://new.halfrost.com/zh/bit_manipulation/</link><pubDate>Sat, 09 Nov 2019 08:27:00 +0000</pubDate><guid>https://new.halfrost.com/zh/bit_manipulation/</guid><description>Bit Manipulation 的 Tips: 异或的特性。第 136 题，第 268 题，第 389 题，第 421 题， x ^ 0 = x x ^ 11111……1111 = ~x x ^ (~x) = 11111……1111 x ^ x = 0 a ^ b = c =&amp;gt; a ^ c = b =&amp;gt; b ^ c = a (交换律) a ^ b ^ c = a ^ (b ^ c) = (a ^ b）^ c (结合律) 构造特殊 Mask，将特殊位置放 0 或 1。 1.</description></item><item><title>Algorithm in LeetCode —— Backtracking</title><link>https://new.halfrost.com/zh/backtracking/</link><pubDate>Sat, 19 Oct 2019 08:20:00 +0000</pubDate><guid>https://new.halfrost.com/zh/backtracking/</guid><description>Backtracking 的 Tips: 排列问题 Permutations。第 46 题，第 47 题。第 60 题，第 526 题，第 996 题。 组合问题 Combination。第 39 题，第 40 题，第 77 题，第 216 题。 排列和组合杂交问题。第 1079 题。 N 皇后终极解法(二进制解法)。第 51 题，第 52 题。 数独问题。第 37 题。 四个方向搜索。第 79 题，第 212 题，第 980 题。 子集合问题。第 78 题，第 90 题。 Trie。第 208 题，第 211 题。 BFS 优化。第 126 题，第 127 题。 DFS 模板。(只是一个例子，不对应任何题) func combinationSum2(candidates []int, target int) [][]int { if len(candidates) == 0 { return [][]int{} } c, res := []int{}, [][]int{} sort.</description></item><item><title>Algorithm in LeetCode —— Stack</title><link>https://new.halfrost.com/zh/stack/</link><pubDate>Sat, 12 Oct 2019 08:15:00 +0000</pubDate><guid>https://new.halfrost.com/zh/stack/</guid><description>Stack 的 Tips: 括号匹配问题及类似问题。第 20 题，第 921 题，第 1021 题。 栈的基本 pop 和 push 操作。第 71 题，第 150 题，第 155 题，第 224 题，第 225 题，第 232 题，第 946 题，第 1047 题。 利用栈进行编码问题。第 394 题，第 682 题，第 856 题，第 880 题。 单调栈。利用栈维护一个单调递增或者递减的下标数组。第 84 题，第 456 题，第 496 题，第 503 题，第 739 题，第 901 题，第 907 题，第 1019 题。 Title Solution Difficulty Time Space 收藏 20.</description></item><item><title>Algorithm in LeetCode —— Two Pointers</title><link>https://new.halfrost.com/zh/two_pointers/</link><pubDate>Sat, 21 Sep 2019 10:07:00 +0000</pubDate><guid>https://new.halfrost.com/zh/two_pointers/</guid><description>Two Pointers 的 Tips: 双指针滑动窗口的经典写法。右指针不断往右移，移动到不能往右移动为止(具体条件根据题目而定)。当右指针到最右边以后，开始挪动左指针，释放窗口左边界。第 3 题，第 76 题，第 209 题，第 424 题，第 438 题，第 567 题，第 713 题，第 763 题，第 845 题，第 881 题，第 904 题，第 978 题，第 992 题，第 1004 题，第 1040 题，第 1052 题。 left, right := 0, -1 for left &amp;lt; len(s) { if right+1 &amp;lt; len(s) &amp;amp;&amp;amp; freq[s[right+1]-&amp;#39;a&amp;#39;] == 0 { freq[s[right+1]-&amp;#39;a&amp;#39;]++ right++ } else { freq[s[left]-&amp;#39;a&amp;#39;]-- left++ } result = max(result, right-left+1) } 快慢指针可以查找重复数字，时间复杂度 O(n)，第 287 题。 替换字母以后，相同字母能出现连续最长的长度。第 424 题。 SUM 问题集。第 1 题，第 15 题，第 16 题，第 18 题，第 167 题，第 923 题，第 1074 题。 Title Solution Difficulty Time Space 收藏 3.</description></item><item><title>Algorithm in LeetCode —— Sort</title><link>https://new.halfrost.com/zh/sort/</link><pubDate>Sat, 14 Sep 2019 10:00:00 +0000</pubDate><guid>https://new.halfrost.com/zh/sort/</guid><description>Sort 的 Tips: 深刻的理解多路快排。第 75 题。 链表的排序，插入排序(第 147 题)和归并排序(第 148 题) 桶排序和基数排序。第 164 题。 &amp;ldquo;摆动排序&amp;rdquo;。第 324 题。 两两不相邻的排序。第 767 题，第 1054 题。 &amp;ldquo;饼子排序&amp;rdquo;。第 969 题。 Title Solution Difficulty Time Space 收藏 56. Merge Intervals Go Medium O(n log n) O(log n) 57. Insert Interval Go Hard O(n) O(1) 75. Sort Colors Go Medium O(n) O(1) ❤️ 147.</description></item><item><title>Algorithm in LeetCode —— Linked List</title><link>https://new.halfrost.com/zh/linked_list/</link><pubDate>Sun, 18 Aug 2019 07:47:00 +0000</pubDate><guid>https://new.halfrost.com/zh/linked_list/</guid><description>Linked List 的 Tips: 巧妙的构造虚拟头结点。可以使遍历处理逻辑更加统一。 灵活使用递归。构造递归条件，使用递归可以巧妙的解题。不过需要注意有些题目不能使用递归，因为递归深度太深会导致超时和栈溢出。 链表区间逆序。第 92 题。 链表寻找中间节点。第 876 题。链表寻找倒数第 n 个节点。第 19 题。只需要一次遍历就可以得到答案。 合并 K 个有序链表。第 21 题，第 23 题。 链表归类。第 86 题，第 328 题。 链表排序，时间复杂度要求 O(n * log n)，空间复杂度 O(1)。只有一种做法，归并排序，至顶向下归并。第 148 题。 判断链表是否存在环，如果有环，输出环的交叉点的下标；判断 2 个链表是否有交叉点，如果有交叉点，输出交叉点。第 141 题，第 142 题，第 160 题。 Title Solution Difficulty Time Space 收藏 2. Add Two Numbers Go Medium O(n) O(1) 19.</description></item><item><title>TLS Application-Layer Protocol Negotiation Extension</title><link>https://new.halfrost.com/zh/tls_alpn/</link><pubDate>Sun, 11 Aug 2019 07:43:00 +0000</pubDate><guid>https://new.halfrost.com/zh/tls_alpn/</guid><description>这篇文章我们主要来讨论讨论 Transport Layer Security (TLS) 握手中的 Application-Layer Protocol Negotiation 扩展。对于在同一 TCP 或 UDP 端口上支持多个应用程序协议的实例，此扩展允许应用程序层去协商将在 TLS 连接中使用哪个协议。
一. Introduction 应用层协议越来越多地封装在 TLS 协议 [RFC5246] 中。这种封装使应用程序可以使用几乎整个全球 IP 基础结构中已经存在的现有安全通信链路的 443 端口。
当单个服务器端端口号（例如端口 443）上支持多个应用程序协议时，客户端和服务器需要协商用于每个连接的应用程序协议。希望在不增加客户端和服务器之间的网络往返次数的情况下完成此协商，因为每次往返都会降低最终用户的体验。此外，允许基于协商的应用协议来选择证书将是有利的。
本文指定了 TLS 扩展，该扩展允许应用程序层在 TLS 握手中协商协议的选择。HTTPbis WG 要求进行这项工作，以解决通过 TLS 进行 HTTP/2（[HTTP2]）的协商。但是，ALPN 有助于协商任意应用程序层协议。
借助 ALPN，客户端会将支持的应用程序协议列表作为 TLS ClientHello 消息的一部分发送。服务器选择一个协议，并将所选协议作为 TLS ServerHello 消息的一部分发送。因此，可以在 TLS 握手中完成应用协议协商，而无需添加网络往返，并且允许服务器根据需要，将不同的证书与每个应用协议相关联。
二. Application-Layer Protocol Negotiation 1. The Application-Layer Protocol Negotiation Extension 定义了一个新的扩展类型(&amp;ldquo;application_layer_protocol_negotiation(16)&amp;quot;)，客户端可以在其 “ClientHello” 消息中包含该扩展类型。
enum { application_layer_protocol_negotiation(16), (65535) } ExtensionType; (&amp;ldquo;application_layer_protocol_negotiation(16)&amp;quot;) 扩展名的 &amp;ldquo;extension_data&amp;rdquo; 字段应包含 &amp;ldquo;ProtocolNameList&amp;rdquo; 值。</description></item><item><title>HPACK: Header Compression for HTTP/2</title><link>https://new.halfrost.com/zh/http2_rfc7541/</link><pubDate>Sun, 21 Jul 2019 07:41:00 +0000</pubDate><guid>https://new.halfrost.com/zh/http2_rfc7541/</guid><description>Table of Contents 1. Introduction 1.1. Overview 1.2. Conventions 1.3. Terminology 2. Compression Process Overview 2.1. Header List Ordering 2.2. Encoding and Decoding Contexts 2.3. Indexing Tables 2.3.1. Static Table 2.3.2. Dynamic Table 2.3.3. Index Address Space 2.4. Header Field Representation 3. Header Block Decoding 3.1. Header Block Processing 3.2. Header Field Representation Processing 4. Dynamic Table Management 4.1. Calculating Table Size 4.2. Maximum Table Size 4.</description></item><item><title>HTTP/2 HPACK 实际应用举例</title><link>https://new.halfrost.com/zh/http2-hpack-example/</link><pubDate>Sun, 14 Jul 2019 07:37:00 +0000</pubDate><guid>https://new.halfrost.com/zh/http2-hpack-example/</guid><description>在上篇文章中，具体说明了 HPACK 算法中的 8 种场景(7 种 Name-value 的场景 + 1 种动态表更新场景)。
动态表大小更新有两种方式，一种是在 HEADERS 帧中直接修改(“001” 3 位模式开始)，另外一种方式是通过 SETTINGS 帧中的 SETTINGS_HEADER_TABLE_SIZE 中设置的。
在介绍 HPACK 实际应用之前，需要先来看看静态表的定义和 HTTP/2 中霍夫曼编码的定义。
一. 静态表定义 静态表（请参阅第 2.3.1 节）包含一个预定义且不可更改的 header 字段列表。
静态表是根据流行网站使用的最频繁的 header 字段创建的，并添加了 HTTP/2 特定的伪 header 字段（请参见 [HTTP2]的 8.1.2.1 节）。对于具有一些频繁值的 header 字段，为每个这些频繁值添加了一个条目。对于其他标题字段，添加了带有空值的条目。
表 1 列出了构成静态表的预定义 header 字段，并提供了每个条目的索引。
Index Header Name Header Value 1 :authority 2 :method GET 3 :method POST 4 :path / 5 :path /index.</description></item><item><title>详解 HTTP/2 头压缩算法 —— HPACK</title><link>https://new.halfrost.com/zh/http2-header-compression/</link><pubDate>Sun, 16 Jun 2019 07:31:00 +0000</pubDate><guid>https://new.halfrost.com/zh/http2-header-compression/</guid><description>一. 简介 在 HTTP/1.1（请参阅[RFC7230]）中，header 字段未被压缩。随着网页内的请求数增长到需要数十到数百个请求的时候，这些请求中的冗余 header 字段不必要地消耗了带宽，从而显着增加了延迟。
SPDY [SPDY] 最初通过使用 DEFLATE [DEFLATE] 格式压缩 header 字段来解决此冗余问题，事实证明，这种格式非常有效地表示了冗余 header 字段。但是，这种方法暴露了安全风险，如 CRIME（轻松实现压缩率信息泄漏）攻击所证明的安全风险（请参阅 [CRIME]）。
本规范定义了 HPACK，这是一种新的压缩方法，它消除了多余的 header 字段，将漏洞限制到已知的安全攻击，并且在受限的环境中具有有限的内存需求。第 7 节介绍了 HPACK 的潜在安全问题。
HPACK 格式特意被设计成简单且不灵活的形式。两种特性都降低了由于实现错误而引起的互操作性或安全性问题的风险。没有定义扩展机制；只能通过定义完整的替换来更改格式。
1. 总览 本规范中定义的格式将 header 字段列表视为 name-value 对的有序集合，其中可以包括重复的对。名称和值被认为是八位字节的不透明序列，并且 header 字段的顺序在压缩和解压缩后保持不变。
header 字段表将 header 字段映射到索引值，从而得到编码。这些 header 字段表可以在编码或解码新 header 字段时进行增量更新。
在编码形式中，header 字段以字面形式表示或作为对 header 字段表中的一个 header 字段的引用。因此，可以使用引用和字面值的混合来编码 header 字段的列表。
字面值可以直接编码，也可以使用静态霍夫曼编码(最高压缩比 8:5)。
编码器负责决定将哪些 header 字段作为新条目插入 header 字段表中。解码器执行对编码器指定的 header 字段表的修改，从而在此过程中重建 header 字段的列表。这使解码器保持简单并可以与多种编码器互操作。
附录C 中提供了使用这些不同的机制表示 header 字段的示例。
注：在 HTTP/2 中，请求和响应标头字段的定义保持不变，仅有一些微小的差异：所有标头字段名称均为小写，请求行现在拆分成各个 :method、:scheme、:authority 和 :path 伪标头字段。</description></item><item><title>Hypertext Transfer Protocol Version 2 (HTTP/2)</title><link>https://new.halfrost.com/zh/http2_rfc7540/</link><pubDate>Sun, 09 Jun 2019 07:28:00 +0000</pubDate><guid>https://new.halfrost.com/zh/http2_rfc7540/</guid><description>Table of Contents 1. Introduction 2. HTTP/2 Protocol Overview 2.1. Document Organization 2.2. Conventions and Terminology 3. Starting HTTP/2 3.1. HTTP/2 Version Identification 3.2. Starting HTTP/2 for &amp;ldquo;http&amp;rdquo; URIs 3.2.1 HTTP2-Settings Header Field 3.3. Starting HTTP/2 for &amp;ldquo;https&amp;rdquo; URIs 3.4. Starting HTTP/2 with Prior Knowledge 3.5. HTTP/2 Connection Preface 4. HTTP Frames 4.1. Frame Format 4.2. Frame Size 4.3. Header Compression and Decompression 5. Streams and Multiplexing 5.</description></item><item><title>HTTP/2 中的常见问题</title><link>https://new.halfrost.com/zh/http2-frequently-asked-questions/</link><pubDate>Sun, 26 May 2019 07:25:00 +0000</pubDate><guid>https://new.halfrost.com/zh/http2-frequently-asked-questions/</guid><description>以下是有关 HTTP/2 的常见问题。
一. 一般的问题 1. 为什么要修改 HTTP？ HTTP/1.1 在 Web 上已经服务了 15 年以上，但是它的缺点正在开始显现。加载网页比以往任何时候都需要更多资源(请参阅HTTP Archive’s page size statistics)，并且要高效地加载所有这些资源非常困难，因为事实上，HTTP 只允许每个 TCP 连接有一个未完成的请求。
过去，浏览器使用多个 TCP 连接来发出并行请求。但是，这是有局限性的。如果使用的连接过多，则将适得其反(TCP 拥塞控制将被无效化，导致的用塞事件将会损害性能和网络)，并且从根本上讲是不公平的(因为浏览器会占用许多本不该属于它的资源)。同时，大量请求意味着“在线”上有大量重复数据。
这两个因素都意味着 HTTP/1.1 请求有很多与之相关的开销。如果请求过多，则会影响性能。
这使得业界误解了“最佳实践”，进行诸如 spriting 图片合并，data: inlining 内联数据，Domain Sharding 域名分片和 Concatenation 文件合并之类的事情。这些 hack 行为表明协议本身存在潜在问题，在使用的时候会出现很多问题。
2. 谁制定了 HTTP/2？ HTTP/2 是由 IETF 的 HTTP 工作组开发的，该工作组维护 HTTP 协议。它由许多 HTTP 实现者，用户，网络运营商和 HTTP 专家组成。
请注意，虽然我们的邮件列表托管在 W3C 网站上，但这并不是 W3C 的努力。但是，Tim Berners-Lee 和 W3C TAG 与 WG 的工作进度保持同步。
大量的人为这项工作做出了贡献，最活跃的参与者包括来自诸如 Firefox，Chrome，Twitter，Microsoft 的 HTTP stack，Curl 和 Akamai 等“大型”项目的工程师，以及许多诸如 Python、Ruby 和 NodeJS 之类的 HTTP 实现者。</description></item><item><title>HTTP/2 中的注意事项</title><link>https://new.halfrost.com/zh/http2-considerations/</link><pubDate>Sun, 19 May 2019 07:15:00 +0000</pubDate><guid>https://new.halfrost.com/zh/http2-considerations/</guid><description>一. HTTP 值得关注的问题 本节概述了 HTTP 协议的属性，这些属性可提高互操作性，减少已知安全漏洞的风险或降低实现方在代码实现的时候出现歧义的可能性。
1. 连接管理 HTTP/2 连接是持久的。为了获得最佳性能，建议客户端不要主动关闭连接。除非在确定不需要与服务器进行进一步通信(例如，当用户离开特定网页时)或服务器关闭连接的时候再去关闭连接。
客户端不应该打开与给定主机和端口对的多个 HTTP/2 连接，其中主机包括是从 URI，选定的备用服务 ALT-SVC 或配置的代理中派生出来的。
客户端可以创建其他连接作为替换，以替换可用的流标识符空间即将用完的连接（第 5.1.1 节），刷新 TLS 连接的密钥材料，或替换遇到错误的连接（第 5.4.1 节）。
客户端可以对一个 IP 打开多个连接，并且 TCP 端口可以使用不同服务器标识 TLS-EXT 或者提供不同的 TLS 客户端证书，但应该避免使用相同的配置创建多个连接。
鼓励服务器尽可能长时间地保持打开的连接，但如果有需要，允许服务器终止空闲连接。当任一端点选择关闭传输层 TCP 连接时，发起终止的端点应首先发送 GOAWAY 帧（第 6.8 节），这样做能够使得两个端点可以可靠地确定先前发送的帧是否已被处理并正常完成或者终止任何必要的剩余任务。
(1). 连接重用 直接或通过使用 CONNECT 方法创建的隧道的方式（第 8.3 节）对原始服务器建立的连接，可以重用于具有多个不同 URI 权限组件的请求。只要原始服务器具有权限，就可以重用连接（第 10.1 节）。对于没有 TLS 的 TCP 连接，这取决于已解析为相同 IP 地址的主机。
对于 &amp;ldquo;https&amp;rdquo; 资源，连接重用还取决于具有对 URI 中的主机的证书是否有效。服务器提供的证书必须要能通过客户端在 URI 中为主机建立新的 TLS 连接时将执行的任何检查。
源服务器可能提供具有多个 &amp;ldquo;subjectAltName&amp;rdquo; 属性的证书或带有通配符的名称，其中一个对 URI 中的权限有效。例如，&amp;ldquo;subjectAltName&amp;rdquo; 为 &amp;ldquo;* .</description></item><item><title>HTTP/2 中的 HTTP 语义</title><link>https://new.halfrost.com/zh/http2-http-semantics/</link><pubDate>Sun, 12 May 2019 07:04:00 +0000</pubDate><guid>https://new.halfrost.com/zh/http2-http-semantics/</guid><description>HTTP/2 协议设计之初就为了与当前使用的 HTTP 尽可能兼容。这意味着，从应用程序的角度来看， 协议的功能基本没有变化。为了实现这一点，所有请求和响应语义被保留，虽然传达的语法那些语义已经改变了。
因此，HTTP/1.1 中的语义和内容 [RFC7231]，条件请求 [RFC7232]，范围请求 [RFC7233]，缓存 [RFC7234] 和认证 [RFC7235] 的规范和要求同样适用于 HTTP/2。HTTP/1.1 消息语法和路由 [RFC7230] 的选定部分（例如 HTTP 和 HTTPS URI 方案）也适用于HTTP/2，但此协议的语义表达式在下文中定义。
一. HTTP Request/Response Exchange 客户端使用先前未使用的流标识符在新流上发送 HTTP 请求 (第 5.1.1 节)。服务器在与请求相同的流上发送 HTTP 响应。
HTTP消息（请求或响应）包括：
仅用于响应，零个或多个 HEADERS 帧（每个帧后跟零个或多个 CONTINUATION 帧）包含信息（1xx）HTTP 响应的消息头（参见 [RFC7230] 第 3.2 节和[RFC7231] 第 6.2 节）， 一个包含消息头的 HEADERS 帧（后跟零个或多个 CONTINUATION 帧）（参见 [RFC7230] 第 3.2 节） 包含有效载荷主体 payload body 的零个或多个 DATA 帧（参见 [RFC7230] 第 3.3 节) 可选地，一个 HEADERS 帧，然后是零或更多 CONTINUATION 帧包含 trailer-part（如果有）（请参阅 [RFC7230] 第 4.</description></item><item><title>HTTP/2 中的帧定义</title><link>https://new.halfrost.com/zh/http2-http-frames-definitions/</link><pubDate>Sun, 28 Apr 2019 07:02:00 +0000</pubDate><guid>https://new.halfrost.com/zh/http2-http-frames-definitions/</guid><description>在 HTTP/2 的规范中定义了许多帧类型，每个帧类型由唯一的 8 位类型代码标识。每种帧类型在建立和管理整个连接或单个 stream 流中起到不同的作用。
特定的帧类型的传输可以改变连接的状态。如果端点无法维持连接状态的同步视图，则无法在连接内继续成功通信。因此，重要的是端点必须共享的理解状态，在使用了任何给定帧的情况下，这些状态是如何受到它们影响的。
Connection 连接:1 个 TCP 连接，包含 1 个或者多个 stream。所有通信都在一个 TCP 连接上完成，此连接可以承载任意数量的双向数据流。
Stream 数据流：一个双向通信的数据流，包含 1 条或者多条 Message。每个数据流都有一个唯一的标识符和可选的优先级信息，用于承载双向消息。
Message 消息：对应 HTTP/1.1 中的请求 request 或者响应 response，包含 1 条或者多条 Frame。
Frame 数据帧：最小通信单位，以二进制压缩格式存放内容。来自不同数据流的帧可以交错发送，然后再根据每个帧头的数据流标识符重新组装。
在 HTTP/1.1 中的一个消息是由 Start Line + header + body 组成的，而 HTTP/2 中一个消息是由 HEADER frame + 若干个 DATA frame 组成的，如下图：
HTTP/2 所有性能增强的核心在于新的二进制分帧层，它定义了如何封装 HTTP 消息并在客户端与服务器之间传输。这里所谓的“层”，指的是位于套接字接口与应用可见的高级 HTTP API 之间一个经过优化的新编码机制：HTTP 的语义（包括各种动词、方法、标头）都不受影响，不同的是传输期间对它们的编码方式变了。 HTTP/1.x 协议以换行符作为纯文本的分隔符，而 HTTP/2 将所有传输的信息分割为更小的消息和帧，并采用二进制格式对它们编码。
这样一来，客户端和服务器为了相互理解，都必须使用新的二进制编码机制：HTTP/1.x 客户端无法理解只支持 HTTP/2 的服务器，反之亦然。不过不要紧，现有的应用不必担心这些变化，因为客户端和服务器会替我们完成必要的分帧工作。</description></item><item><title>HTTP/2 中的 HTTP 帧和流的多路复用</title><link>https://new.halfrost.com/zh/http2-http-frames/</link><pubDate>Sun, 21 Apr 2019 06:57:00 +0000</pubDate><guid>https://new.halfrost.com/zh/http2-http-frames/</guid><description>上篇文章中讲的 HTTP/2 是如何建立连接的。这篇文章开始，我们来讨论讨论帧结构。一旦建立了 HTTP/2 连接后，端点就可以开始交换帧了。
一. Frame Format 帧格式 HTTP/2 会发送有着不同类型的二进制帧，但他们都有如下的公共字段：Type, Length, Flags, Stream Identifier 和 frame payload。本规范中一共定义了 10 种不同的帧，其中最基础的两种分别对应于 HTTP 1.1 的 DATA 和 HEADERS。
所有帧都以固定的 9 字节大小的头作为帧开始，后跟可变长度的有效载荷 payload。
+-----------------------------------------------+ | Length (24) | +---------------+---------------+---------------+ | Type (8) | Flags (8) | +-+-------------+---------------+-------------------------------+ |R| Stream Identifier (31) | +=+=============================================================+ | Frame Payload (0...) ... +---------------------------------------------------------------+ 帧头的字段定义如下：
Length：
帧有效负载的长度表示为无符号的 24 位整数。除非接收方为 SETTINGS_MAX_FRAME_SIZE 设置了较大的值(详情见这里)，否则不得发送大于2 ^ 14（16,384）的值。帧头的 9 个八位字节不包含在此长度值中。
Type：</description></item><item><title>解开 HTTP/2 的面纱：HTTP/2 是如何建立连接的</title><link>https://new.halfrost.com/zh/http2_begin/</link><pubDate>Sun, 14 Apr 2019 06:54:00 +0000</pubDate><guid>https://new.halfrost.com/zh/http2_begin/</guid><description>超文本传输协议(HTTP)是一种非常成功的协议。 但是，HTTP/1.1 使用底层传输的方式([RFC7230]，第 6 节)，其中有几个特性对今天的应用程序性能有负面影响。
特别是，HTTP/1.0 在给定的 TCP 连接上一次只允许一个请求未完成。HTTP/1.1 添加了请求流水线操作(request pipelining)，但这只是部分地解决了请求并发性，并且仍然受到队首阻塞的影响。因此，需要发出许多请求的 HTTP/1.0 和 HTTP/1.1 客户端使用多个连接到服务器以实现并发，从而减少延迟。
此外，HTTP 头字段通常是重复且冗长的，导致不必要的网络流量以及导致初始 TCP 拥塞窗口被快速的填满。当在新的 TCP 连接上发出多个请求时，这可能导致过多的延迟。
HTTP/2 通过定义了一个优化过的 HTTP 语义，它与底层连接映射，用这种方式来解决这些问题。具体而言，它允许在同一连接上交错请求和响应消息，并使用 HTTP 头字段的有效编码。它还允许对请求进行优先级排序，使更多重要请求更快地完成，从而进一步提高性能。
HTTP/2 对网络更友好，因为与 HTTP/1.x 相比，可以使用更少的 TCP 连接。这意味着与其他流量和长连接的竞争减少，反过来可以更好地利用可用网络容量。最后，HTTP/2 还可以通过使用二进制消息帧来更有效地处理消息。
HTTP/2 最大限度的兼容 HTTP/1.1 原有行为：
在应用层上修改，基于并充分挖掘 TCP 协议性能。 客户端向服务端发送 request 请求的模型没有变化。 scheme 没有发生变化，没有 http2:// 使用 HTTP/1.X 的客户端和服务器可以无缝的通过代理方式转接到 HTTP/2 上。 不识别 HTTP/2 的代理服务器可以将请求降级到 HTTP/1.X。 一. HTTP/2 Protocol Overview HTTP/2 为 HTTP 语义提供了优化的传输。 HTTP/2 支持 HTTP/1.</description></item><item><title>HTTPS 温故知新（六） —— TLS 中的 Extensions</title><link>https://new.halfrost.com/zh/https-extensions/</link><pubDate>Sun, 17 Mar 2019 03:08:00 +0000</pubDate><guid>https://new.halfrost.com/zh/https-extensions/</guid><description>扩展是 TLS 比较重要的一个知识点。它的存在能让 Client 和 Server 在不更新 TLS 的基础上，获得新的能力。扩展的官方文档在 [RFC 6066] 中定义。Extension 像 TLS 中的一系列可水平扩展的插件。
Client 在 ClientHello 中申明多个自己可以支持的 Extension，以向 Server 表示自己有以下这些能力，或者向 Server 协商某些协议。Server 收到 ClientHello 以后，依次解析 Extension，有些如果需要立即回应，就在 ServerHello 中作出回应，有些不需要回应，或者 Server 不支持的 Extension 就不用响应，忽略不处理。
TLS 握手中的 Extension 有以下几个特点:
Extension 不影响 TLS 握手的成功与否。Server 对 ClientHello 中的 Extension 有些不支持，忽略不处理即可，不影响握手的流程。
ServerHello 中回应 Client 的 Extension 一定要是 ClientHello 中的 Extension 的子集(小于等于)。ServerHello 中禁止出现 ClientHello 中没有出现的 Extension。如果一个 Client 在 ServerHello 中收到一个扩展类型但在相关的 ClientHello 中并没有请求，它必须用一个 unsupported_extension 致命 alert 消息来丢弃握手。</description></item><item><title>HTTPS 温故知新（五） —— TLS 中的密钥计算</title><link>https://new.halfrost.com/zh/https-key-cipher/</link><pubDate>Sun, 10 Mar 2019 03:00:00 +0000</pubDate><guid>https://new.halfrost.com/zh/https-key-cipher/</guid><description>本篇文章我们来对比对比 TLS 1.2 和 TLS 1.3 中的密钥计算。
一. TLS 1.2 中的密钥 在 TLS 1.2 中，有 3 种密钥：预备主密钥、主密钥和会话密钥(密钥块)，这几个密钥都是有联系的。
struct { uint32 gmt_unix_time; opaque random_bytes[28]; } Random; struct { ProtocolVersion client_version; opaque random[46]; } PreMasterSecret; struct { uint8 major; uint8 minor; } ProtocolVersion; 对于 RSA 握手协商算法来说，Client 会生成的一个 48 字节的预备主密钥，其中前 2 个字节是 ProtocolVersion，后 46 字节是随机数，用 Server 的公钥加密之后通过 Client Key Exchange 子消息发给 Server，Server 用私钥来解密。对于 (EC)DHE 来说，预备主密钥是双方通过椭圆曲线算法生成的，双方各自生成临时公私钥对，保留私钥，将公钥发给对方，然后就可以用自己的私钥以及对方的公钥通过椭圆曲线算法来生成预备主密钥，预备主密钥长度取决于 DH/ECDH 算法公钥。预备主密钥长度是 48 字节或者 X 字节。
主密钥是由预备主密钥、ClientHello random 和 ServerHello random 通过 PRF 函数生成的。主密钥长度是 48 字节。可以看出，只要我们知道预备主密钥或者主密钥便可以解密抓包数据，所以 TLS 1.</description></item><item><title>【2018年终总结】如何看待软件开发 ？</title><link>https://new.halfrost.com/zh/halfrost_2018/</link><pubDate>Sun, 10 Feb 2019 00:02:00 +0000</pubDate><guid>https://new.halfrost.com/zh/halfrost_2018/</guid><description>题记 考虑到本系列文章有部分新的读者，所以关于本系列文章名字的起源就不再赘述了，见这里《&amp;ldquo;星霜荏苒&amp;quot;名字诞生记》
今年的总结主要想和读者聊聊如何看待软件开发，回答去年年终总结文末的问题。这个话题也比较大，每个开发人员也都有自己的答案。笔者根据自己刚刚从业几年的经验说说自己的看法，作为一个开发萌新，见解略短浅，可能会贻笑大方。欢迎大家指点。
软件开发是什么？ 软件开发是一个创造或者维护，应用，框架或者应用组件的过程中涉及到的需求分析，设计，编码实现，测试，bug 修复的过程。软件开发是编写代码和维护代码的过程。更广义的来说，软件开发是一种人类思维活动的体现。
软件开发与其说是搬砖，不如说是处理问题的能力，智商的体现。开发什么并不重要，重要的是思考问题的角度和快速解决问题的能力。使用过的前后端和客户端的编程语言之后，笔者感受到会使用语言并没有什么，能用什么语言解决多大的问题才是关键。前端后端都有相应的职级，相同的职级，不同的开发岗薪资差距不大。职级的高低更多的展现的是一个人思维活动能力强弱的体现。而且各个领域和方向，干到高级开发都不容易，每个领域都有各自的 roadmap，在一个领域深耕都需要静下心来 2-3 年。谁能一直领先并且一直维持在金字塔顶端，都是非常不容易的事情。
广义的来说，开发用什么语言仅仅是一个进入这个行业的首秀，之后往下走，会接触到很多其他语言，如何修炼思维能力才是一个软件开发技术人需要关注的东西。菜鸟和大神的差距在于有效时间的积累，经常有这种情况，菜鸟和大神同时遇到一个同一个问题，哪怕是陌生的问题，大神也可以很快的找到问题的本质。大神解决问题以后，说靠的是自己的“直觉”找到的突破口。但就是这种直觉就是宝贵的经验，这就是菜鸟们需要用时间积累的东西。这种“直觉”并不是玄学，是一种能力，经验丰富以后带来的快速解决问题的能力。
在笔者经历过三端的开发迭代以后，综合看客户端，前端，后端，三端开发流程和工作内容都有相同的地方。
开发流程三端都一致的。评审，排期，kickoff，站会，开发，确定终版，提测，灰度，上线发布。
各端都有 APM，都有监控性能的需求。不过架构实现方式不同。三端关注的点是不同的，客户端和前端更加关注客户为主，用户体验，页面打开速度等等。服务端关注点以服务为主，服务性能，可用性，高并发，低延迟，io 读写速度，多活，跨机房等等。这里可能会有读者说鄙视链的问题，笔者认为没有必要对其他端的鄙视。做纯服务端的开发人员对图形图形和像素就不太敏感，让他们来做一些前端动画，可能比较难。做纯前端的开发人员对后端的架构可能不太熟悉，让他们设计一些大型的高并发系统，可能比较难。(考虑到读者里面有全栈开发，对三端都非常了解，所以这里特意加了“纯”字)让做纯服务端开发的写前端，不一定写的来；让纯客户端开发写服务端，也不一定写的来。所以各端有各端的难处，可以相互学习，但是没必要鄙视。
综上，软件开发狭义的看，是实现需求到最终上线发布的过程，广义的看，是将人类的思维活动固化凝结成软件产品的过程。软件开发的过程中不断的训练人的思维和发现问题解决问题的能力。
好了，至此我关于这个问题的答案都述说完了。接下来的行文逻辑和去年不同，笔者打算写写今年发生的一些“大事”，以及分享一些所见所闻所想和一些憋着也没啥机会说只能放在总结里面的话，有些是周围朋友或者群里常常讨论的问题，我对这些问题也都有自己的看法，写成文字记录下来。我的答案可能全错，读者看完如果能有收获，这篇文章的目的也算达到了。
拥抱变化 过去一年被问的最多的就是“贵司被收购了，是不是要 XXX ？”，XXX 是周围的朋友猜想的很多事情，例如，合并，裁员，N+1，离职……等等。从 2018 年 1 月到 2 月底宣布被阿里收购，公司内部确实发生的非常多的事情，组织架构变化很大。被收购以后内部变化也非常大。当然这也都算是常规操作吧，毕竟收购以后有很多资源合并的流程。和笔者关系比较密切的一些人和一些事都发生了巨大的变化，也许因为年后正常人员流动吧，一个月内微信通讯录里同事的公司备注更换了好多。一起加班奋斗到深夜的亲密“战友”离我而去，晚上大半夜还在公交车站一起讨论问题的战友，第二天醒来却和你说要离开了。笔者是一个比较重感情的人，毕竟我们是一个 team，一起闯出来的“天下”，“出生入死”锻造出来的铁打的感情，说“分手”就“分手”了，心里确实不好受。当知道离开的原因以后只能一声叹息，心里也只能祝福他在新的公司里面大展宏图。那段时间身边的气氛实在压抑，于是笔者去了一趟日本，努力放下一切，一路沿着樱花开放的方向，泡着温泉。当然，越想忘记的事情反而越加记忆深刻。现在 2018 年底了，回过头来再看这段时光，对笔者影响只是朋友之间感情的影响，其他影响基本没有，组织架构的变化影响最大的是上层，工头那一层，对我们这些底层搬砖工和水泥工的影响不大，不老老实实搬砖，多想什么呢？
关于自身技术上的变更也比较多。年头的时候打算参与一个人工智能相关的项目。于是自己看了西瓜书和一些视频，入门了一下人工智能。大多数入门知识还比较好理解，都是高等数学、线性代数、概率论的知识。笔者考研期间把这些知识都好好复习过，遗忘的不多，捡起来也快。后来因为一些组织架构的变更，没能参与这个项目。2017 年比较火的技术是人工智能 AI 和区块链，笔者在年初的时候也在 kindle 上看了 4 本区块链入门的书，区块链底层技术都不算全新的技术，只是它的设计和理念比较新颖。看完几本区块链入门的书以后，对底层加密技术比较感兴趣，又去重新捡起了密码学。看了 3 本密码学相关的技术书籍。虽然笔者没有投身区块链行业，但是密码学的知识作为计算机的基础知识，能扩展的领域也非常多。现在人们对安全的要求越来越高，网络安全，信息安全，无一不和加密有关。到了 5 月份加入了新的项目组以后，定下了自己下半年的 KPI ，下半年的目标也比较一致和明确了。我参与了一个和网络相关的项目，性能是重中之重，网络耗时也是我优化的重点。HTTPS 如果慢，可能慢在哪里？为什么会慢？和加密的密钥套件选择有什么关系？TLS 1.3 为什么能使整个请求时间变短？QUIC 为什么在弱网环境下效果很好？蜂窝网络是如何选择基站的？蜂窝网络中信号回落是怎么一回事？这些问题我之前只有模模糊糊的答案，重度参与了这个项目，经过实践以后，以上问题都有非常深刻的答案了。经过几轮优化，阶段性的结果也得到了业务方的肯定。
2018 年一路学习和工作中的总结见博客博文 Archive 列表吧。回过头来看，这一年收获马马虎虎，在网络相关的收获很大。充满变化的一年，我拥抱了变化。
职业生涯规划和技术人的追求 关于职业生涯的问题，笔者曾经也迷茫过，也请教了不少大佬是如何规划自己职业生涯的。笔者现在不迷茫了。
要想梳理清属于自己的职业生涯规划，需要先想明白作为一个技术人的追求是什么？知道自己心所向，目标明确以后，再指定职业生涯规划就会非常简单了。
一个刚刚毕业的应届生，刚刚进入软件开发的行业，难免会有一些迷茫，不知道自己想要什么，未来的路怎么走。有一位大师曾经这样给我了建议，“毕业前 5 年(最长 5 年)，建议开发的各个方向都多多尝试尝试，找到自己真正感兴趣方向，一旦找到这个方向以后，埋下头来钻进去 3-5 年”。这个做法对迷茫的同学也许有效。对于刚刚毕业就打算进大公司的应届生，笔者给的建议是，第一技能一定要专精。第一技能是进大公司的敲门砖，第一技能如果不够专精，哪怕有 10 个附加技能也没用。进大公司工作只是第一步，之后的发展都看个人规划了，在做好自己本职工作的前提下，多余时间努力钻研感兴趣的方向吧。
关于感兴趣的定义：</description></item><item><title>HTTPS 温故知新（四） —— 直观感受 TLS 握手流程(下)</title><link>https://new.halfrost.com/zh/https_tls1-3_handshake/</link><pubDate>Sat, 02 Feb 2019 23:58:00 +0000</pubDate><guid>https://new.halfrost.com/zh/https_tls1-3_handshake/</guid><description>在 HTTPS 开篇的文章中，笔者分析了 HTTPS 之所以安全的原因是因为 TLS 协议的存在。TLS 能保证信息安全和完整性的协议是记录层协议。(记录层协议在上一篇文章中详细分析了)。看完上篇文章的读者可能会感到疑惑，TLS 协议层加密的密钥是哪里来的呢？客户端和服务端究竟是如何协商 Security Parameters 加密参数的？这篇文章就来详细的分析一下 TLS 1.2 和 TLS 1.3 在 TLS 握手层上的异同点。
TLS 1.3 在 TLS 1.2 的基础上，针对 TLS 握手协议最大的改进在于提升速度和安全性。本篇文章会重点分析这两块。
先简述一下 TLS 1.3 的一些优化和改进:
减少握手等待时间，将握手时间从 2-RTT 降低到 1-RTT，并且增加 0-RTT 模式。
删除 RSA 密钥协商方式，静态的 Diffie-Hellman 密码套件也被删除了。因为 RSA 不支持前向加密性。TLS 1.3 只支持 (EC)DHE 的密钥协商算法。删除了 RSA 的方式以后，能有效预防心脏出血的攻击。所有基于公钥的密钥交换算法现在都能提供前向安全。TLS 1.3 规范中只支持 5 种密钥套件，TLS13-AES-256-GCM-SHA384、TLS13-CHACHA20-POLY1305-SHA256、TLS13-AES-128-GCM-SHA256、TLS13-AES-128-CCM-8-SHA256、TLS13-AES-128-CCM-SHA256，隐藏了非对称加密密钥协商算法，因为默认都是椭圆曲线密钥协商。
删除对称加密中，分组加密和 MAC 导致的一些隐患。在 TLS1.3 之前的版本中，选择的是 MAC-then-Encrypt 方式。但是这种方式带来了一些漏洞，例如 BEAST，一系列填充 oracle 漏洞(Lucky 13 和 Lucky Microseconds)。CBC 模式和填充之间的交互也是 SSLv3 和一些 TLS 实现中广泛宣传的 POODLE 漏洞原因。在 TLS 1.</description></item><item><title>HTTPS 温故知新（三） —— 直观感受 TLS 握手流程(上)</title><link>https://new.halfrost.com/zh/https_tls1-2_handshake/</link><pubDate>Sat, 26 Jan 2019 23:56:00 +0000</pubDate><guid>https://new.halfrost.com/zh/https_tls1-2_handshake/</guid><description>在 HTTPS 开篇的文章中，笔者分析了 HTTPS 之所以安全的原因是因为 TLS 协议的存在。TLS 能保证信息安全和完整性的协议是记录层协议。(记录层协议在上一篇文章中详细分析了)。看完上篇文章的读者可能会感到疑惑，TLS 协议层加密的密钥是哪里来的呢？客户端和服务端究竟是如何协商 Security Parameters 加密参数的？这篇文章就来详细的分析一下 TLS 1.2 和 TLS 1.3 在 TLS 握手层上的异同点。
TLS 1.3 在 TLS 1.2 的基础上，针对 TLS 握手协议最大的改进在于提升速度和安全性。本篇文章会重点分析这两块。
一. TLS 对网络请求速度的影响 由于部署了 HTTPS，传输层增加了 TLS，对一个完成的请求耗时又会多增加一些。具体会增加几个 RTT 呢？
先来看看一个请求从零开始，完整的需要多少个 RTT。假设访问一个 HTTPS 网站，用户从 HTTP 开始访问，到收到第一个 HTTPS 的 Response，大概需要经历一下几个步骤(以目前最主流的 TLS 1.2 为例)：
流程 消耗时间 总计 1. DNS 解析网站域名 1-RTT 2. 访问 HTTP 网页 TCP 握手 1-RTT 3.</description></item><item><title>HTTPS 温故知新（二） —— TLS 记录层协议</title><link>https://new.halfrost.com/zh/https_record_layer/</link><pubDate>Sun, 20 Jan 2019 05:57:00 +0000</pubDate><guid>https://new.halfrost.com/zh/https_record_layer/</guid><description>TLS 记录协议是一个层次化的协议。在每一层中，消息都可能包含长度、描述、内容等字段。记录协议主要功能包括装载了被发送的消息，将数据分片为可管理的块，有选择地压缩数据，应用 MAC，加密，传输最终数据。接收到的数据被解密，验证，解压缩，重组，然后传递给高层应用。
特别需要注意的是一条记录消息的类型 type 和长度 length 是不会被加密保护的，它们是明文的。如果这个信息本身是敏感的，应用设计者可能会希望采取一些措施(填充 padding，覆盖流量cover traffic) 来以减小信息泄露。
这篇文章我们重点来讨论一下 TLS 1.2 和 TLS 1.3 在记录层上的不同。先从共同点开始。
一、TLS 记录层的连接状态 一个 TLS 连接的状态就是 TLS 记录协议的操作环境。它指定了一个压缩算法，一个加密算法，一个 MAC 算法。此外，这些算法的参数必须是已知的：用于 connection 连接的读、写两个方向的 MAC 密钥和块加密密钥。逻辑上总是有4个状态比较突出：可读和可写状态，挂起的读和写状态。所有的记录协议都在可读写状态下处理。挂起状态的安全参数可以通过 TLS 握手协议来设置，而 ChangeCipherSpec 可以有选择地设置当前状态为挂起状态，在这种情况下适当的当前状态被设置，并被挂起状态所替代; 挂起状态随后会被重新初始化为一个空状态。将一个状态未经安全参数的初始化就设置为一个当前状态是非法的。初始当前状态一直会指定不使用加密，压缩或 MAC。
ChangeCipherSpec 在官方 TLS 1.3 的规范中已经去掉了，但是为了兼容老的 TLS 1.2 或者网络中间件，这个协议还可能存在。
简单来说，Client 和 Server 在建立链接之前都处于：
pending read status 待读状态 pending write status 待写状态 一旦接收到对端的 ChangeCipherSpec 消息以后，Client 和 Server 就会开始转换为：
current read status 可读状态 current write status 可写状态 在收到对端的 ChangeCipherSpec 之前，所有的 TLS 握手消息都是明文处理的，没有安全性和完整性的保护。一旦所有的加密参数都准备好，就会转换成可读可写状态，进入到了可读可写状态以后就会开始加密和完整性保护了。</description></item><item><title>HTTPS 温故知新（一） —— 开篇</title><link>https://new.halfrost.com/zh/https-begin/</link><pubDate>Sun, 13 Jan 2019 05:31:00 +0000</pubDate><guid>https://new.halfrost.com/zh/https-begin/</guid><description>一、为什么需要 HTTPS HTTP1.1 有以下安全性问题：
使用明文(不加密)进行通信，内容可能会被窃听； 不验证通信方的身份，通信方的身份有可能遭遇伪装； 无法证明报文的完整性，报文有可能遭篡改。 由于 HTTP 设计之初没有考虑到这几点，所以基于 HTTP 的这些应用都会存在安全问题。
1. 数据没有加密 基于 TCP/IP 的网络，网络各处都会存在被监听的风险。而且如果用 HTTP 协议进行通信，HTTP 本身没有加密功能，所以也无法做到对通信整体(使用 HTTP 协议通信的请求和响应的内容)进行加密。即，HTTP 报文使用明文(指未经过加密的报文)方式发送。
像上图表示的那样，在互联网各个环节都可能被监听。就算是加密通信，也能被监听到通信内容，只不过监听者看到的是密文。要解决 HTTP 上面 3 个大的安全问题，第一步就是要先进行加密通信。于是在传输层增加了一层 SSL（Secure Sockets Layer 安全套接层）/ TLS (Transport Layer Security 安全层传输协议) 来加密 HTTP 的通信内容。
HTTPS (HTTP Secure) 并不是新协议，而是 HTTP 先和 SSL（Secure Sockets Layer 安全套接层）/ TLS (Transport Layer Security 安全层传输协议) 通信，再由 SSL/TLS 和 TCP 通信。也就是说 HTTPS 使用了隧道进行通信。
这个时候可能有同学会有疑问了，为什么不直接对 HTTP 报文进行加密，这样就不需要 SSL/TLS 这一层了。确实，如果直接对 HTTP 报文进行加密也可以做到加密通信，但是虽然解决了第一条，但是后面 2 条就不好解决了。</description></item><item><title>TLS &amp; DTLS Heartbeat Extension</title><link>https://new.halfrost.com/zh/tls_heartbeat/</link><pubDate>Sun, 06 Jan 2019 05:14:00 +0000</pubDate><guid>https://new.halfrost.com/zh/tls_heartbeat/</guid><description>这篇文章我们主要来讨论讨论 Transport Layer Security (TLS) 和 Datagram Transport Layer Security (DTLS) 中的 Heartbeat 扩展。
Heartbeat 扩展为 TLS/DTLS 提供了一种新的协议，允许在不需要重协商的情况下，使用 keep-alive 功能。Heartbeat 扩展也为 path MTU (PMTU) 发现提供了基础。
一. Introduction 这篇文章描述了 [RFC5246] 和 [RFC6347] 中定义的传输层安全性 (TLS) 和数据报传输层安全性 (DTLS) 协议的心跳扩展，以及它们对 [RFC3436]，[RFC5238] 和 [RFC6083] 中描述的特定传输协议的适应性。
DTLS 协议设计的目的是，保护在不可靠的传输协议之上运行的流量数据。通常，此类协议没有会话管理。DTLS 层可用于确定对等方是否仍然 alive 的唯一机制是昂贵的重新协商机制，尤其是当应用程序使用单向流量时。此外，DTLS 需要执行路径 MTU (PMTU) 发现，但在不影响用户消息的传输的情况下，没有特定的消息类型来实现 PMTU 发现。
TLS 是基于可靠的协议，但没有必要的功能可以在没有连续数据传输的情况下保持连接存活。
本文中描述的心跳扩展克服了这些限制。用户可以使用新的 HeartbeatRequest 消息，该消息必须由具有 HeartbeartResponse 的对端立即应答。要执行 PMTU 发现，如 RFC4821 中所述，可以将包含填充的 HeartbeatRequest 消息用作探测包。
二. Heartbeat Hello Extension Hello Extensions 表示支持 Heartbeats。对端不仅可以表明其实现支持Heartbeats，还可以选择是否愿意接收 HeartbeatRequest 消息并使用 HeartbeatResponse 消息进行响应或仅发送 HeartbeatRequest 消息。前者通过使用 peer_allowed_to_send 作为 HeartbeatMode 来表示;后者通过使用 peer_not_allowed_to_send 作为心跳模式来表示。每次重新谈判都可以改变这一决定。HeartbeatRequest 消息禁止发送到已经表明了 peer_not_allowed_to_send 的对端。如果已经表明了 peer_not_allowed_to_send 的端点收到 HeartbeatRequest 消息，则端点应该以静默方式丢弃该消息，并且可以发送 unexpected_message Alert 消息。</description></item><item><title>The Transport Layer Security (TLS) Protocol Version 1.3</title><link>https://new.halfrost.com/zh/tls_1-3_rfc8446/</link><pubDate>Sun, 23 Dec 2018 01:10:00 +0000</pubDate><guid>https://new.halfrost.com/zh/tls_1-3_rfc8446/</guid><description>Table of Contents 1. Introduction 1.1. Conventions and Terminology 1.2. Major Differences from TLS 1.2 1.3. Updates Affecting TLS 1.2 2. Protocol Overview 2.1. Incorrect DHE Share 2.2. Resumption and Pre-Shared Key (PSK) 2.3. 0-RTT Data 3. Presentation Language 这一章都是一些公认的表达语言，笔者觉得读者基本都清楚，所以就不翻译了。
3.1. Basic Block Size 3.2. Miscellaneous 3.3. Numbers 3.4. Vectors 3.5. Enumerateds 3.6. Constructed Types 3.7. Constants 3.8. Variants 4. Handshake Protocol 4.</description></item><item><title>TLS 1.3 Overview of Security Properties</title><link>https://new.halfrost.com/zh/tls_1-3_security_properties/</link><pubDate>Sun, 16 Dec 2018 01:07:00 +0000</pubDate><guid>https://new.halfrost.com/zh/tls_1-3_security_properties/</guid><description>对 TLS 的完整安全性分析超出了本文的讨论范围。在本附录中，我们提供了对所需属性的非正式描述，以及对更加正式定义的研究文献中提出更详细工作的参考。
我们将握手的属性与记录层的属性分开。
一. Handshake TLS 握手是经过身份验证的密钥交换(AKE，Authenticated Key Exchange)协议，旨在提供单向身份验证(仅 Server)和相互身份验证(Client 和 Server)功能。在握手完成时，每一侧都输出其以下值：
一组 &amp;ldquo;会话密钥&amp;rdquo;(从主密钥导出的各种密钥)，可以从中导出一组工作密钥。
一组加密参数（算法等）。
沟通各方的身份标识
我们假设攻击者是一个活跃的网络攻击者，这意味着它可以完全控制双方通信的网络 [RFC3552]。即使在这些条件下，握手也应提供下面列出的属性。请注意，这些属性不一定是独立的，而是反映了协议消费者的需求的。
建立相同的会话密钥：握手需要在握手的两边输出相同的会话密钥集，前提是它在每个端点上成功完成（参见 [CK01]，定义1，第1部分）
会话密钥的保密性：共享会话密钥只能由通信方而不是攻击者所知（参见 [CK01]，定义 1，第 2 部分）。请注意，在单方面验证的连接中，攻击者可以与 Server 建立自己的会话密钥，但这些会话密钥与 Client 建立的会话密钥不同。
对等身份验证：Client 对等身份的视图应该反映 Server 的身份标识。如果 Client 已通过身份验证，则 Server 的对等标识视图应与 Client 的标识匹配。
会话密钥的唯一性：任何两个不同的握手都应该产生不同的，不相关的会话密钥。握手产生的各个会话密钥也应该是不同且独立的。
降级保护：双方的加密参数应该相同，并且应该与在没有攻击的情况下双方进行通信的情况相同（参见 [BBFGKZ16]，定义 8 和 9）
关于长期密钥的前向保密：如果长期密钥材料（在这种情况下，是基于证书的认证模式中的签名密钥或具有 (EC)DHE 模式的 PSK 中的外部/恢复 PSK）在握手后完成后泄露了，只要会话密钥本身已被删除，这不会影响会话密钥的安全性（参见 [DOW92]）。当在 &amp;ldquo;psk_ke&amp;rdquo; PskKeyExchangeMode 中使用 PSK 时，不能满足前向保密属性。
密钥泄露模拟 (KCI，Key Compromise Impersonation) 抵抗性：在通过证书进行相互认证的连接中，泄露一个参与者的长期密钥不应该破坏该参与者在这个给定连接中对通信对端的认证（参见 [HGFS15]）。例如，如果 Client 的签名密钥被泄露，则不应该在随后的握手中模拟任意的 Server 和 Client 进行通信。</description></item><item><title>TLS 1.3 Backward Compatibility</title><link>https://new.halfrost.com/zh/tls_1-3_backward_compatibility/</link><pubDate>Sun, 09 Dec 2018 01:02:00 +0000</pubDate><guid>https://new.halfrost.com/zh/tls_1-3_backward_compatibility/</guid><description>TLS 协议为端点之间的版本协商提供了一个内置机制，让支持不同版本的 TLS 成为了可能。
TLS 1.x 和 SSL 3.0 使用兼容的 ClientHello 消息。只要 ClientHello 消息格式保持兼容并且 Client 和 Server 都至少共同能支持一个协议版本，Server 就可以尝试使用未来版本 TLS 来回应 Client。
TLS 的早期版本使用记录层版本号(TLSPlaintext.legacy_record_version 和 TLSCiphertext.legacy_record_version)用于各种目的。从 TLS 1.3 开始，此字段被废弃了。所有实现都必须忽略 TLSPlaintext.legacy_record_version 的值。TLSCiphertext.legacy_record_version 的值包含在不被保护的附加数据中，但可以忽略或者可以验证，以此匹配固定的常量值。只能使用握手版本执行版本协商(ClientHello.legacy_version 和 ServerHello.legacy_version 以及ClientHello，HelloRetryRequest 和 ServerHello 中的 &amp;ldquo;supported_versions&amp;rdquo; 扩展名)。为了最大限度地提高与旧的端点的互操作性，协商使用 TLS 1.0-1.2 的实现方应该将记录层版本号设置为协商版本，这样做是为了 ServerHello 和以后的所有记录。
为了最大限度地兼容以前的非标准行为和配置错误的部署，所有实现方都应该支持基于本文档中的期望验证认证的方法，即使在处理先前的 TLS 版本的握手时也是如此(参见 第4.4.2.2节)。
TLS 1.2 和之前版本支持 &amp;ldquo;Extended Master Secret&amp;rdquo; RFC7627 扩展，它将握手记录的大部分内容消化为主密钥。因为 TLS 1.3 总是从转录开始到 Server Finish 都在计算哈希，所以同时支持 TLS 1.3 和早期版本的实现方，无论是否使用了 TLS 1.3，都应该表明在其 API 中使用了Extended Master Secret extension。</description></item><item><title>TLS 1.3 Implementation Notes</title><link>https://new.halfrost.com/zh/tls_1-3_implementation_notes/</link><pubDate>Sun, 02 Dec 2018 00:58:00 +0000</pubDate><guid>https://new.halfrost.com/zh/tls_1-3_implementation_notes/</guid><description>一、Cipher Suites 对称密码套件定义了一对 AEAD 算法和与 HKDF 一起使用的哈希算法。密码套件名称遵循 命名惯例：
CipherSuite TLS_AEAD_HASH = VALUE; +-----------+------------------------------------------------+ | Component | Contents | +-----------+------------------------------------------------+ | TLS | The string &amp;#34;TLS&amp;#34; | | | | | AEAD | The AEAD algorithm used for record protection | | | | | HASH | The hash algorithm used with HKDF | | | | | VALUE | The two-byte ID assigned for this cipher suite | +-----------+------------------------------------------------+ 此规范定义了以下用于 TLS 1.</description></item><item><title>TLS 1.3 Compliance Requirements</title><link>https://new.halfrost.com/zh/tls_1-3_compliance_requirements/</link><pubDate>Sun, 25 Nov 2018 00:49:00 +0000</pubDate><guid>https://new.halfrost.com/zh/tls_1-3_compliance_requirements/</guid><description>一. Mandatory-to-Implement Cipher Suites TLS 1.3 中有一些密码套件是强制必须实现的。
在本文下面的描述中，“必须” 代表 MUST，“应该”代表 SHOULD。请读者注意措辞。
在没有应用程序配置文件标准指定的情况下，除此以外都需要满足以下要求：
符合 TLS 标准的应用程序必须实现 TLS_AES_128_GCM_SHA256 [GCM] 密码套件，应该实现TLS_AES_256_GCM_SHA384 [GCM] 和 TLS_CHACHA20_POLY1305_SHA256 [RFC8439] 密码套件（请参阅 附录 B.4）
符合 TLS 标准的应用程序必须支持数字签名 rsa_pkcs1_sha256(用于证书)，rsa_pss_rsae_sha256（用于 CertificateVerify 和 证书）和ecdsa_secp256r1_sha256。一个符合 TLS 标准的应用程序必须支持与 secp256r1 的密钥交换(NIST P-256) 并且应该支持与 X25519 [RFC7748] 的密钥交换。
二. Mandatory-to-Implement Extensions TLS 1.3 中有一些扩展是强制必须实现的。
如果没有另外指定的应用程序配置文件标准，符合 TLS 标准的应用程序必须实现以下 TLS 扩展：
支持的版本（&amp;ldquo;supported_versions&amp;rdquo;; 第 4.2.1 节）
Cookie（&amp;ldquo;cookie&amp;rdquo;;第 4.2.2 节）
签名算法（&amp;ldquo;signature_algorithms&amp;rdquo;; 第 4.2.3 节）</description></item><item><title>TLS 1.3 0-RTT and Anti-Replay</title><link>https://new.halfrost.com/zh/tls_1-3_0-rtt/</link><pubDate>Sun, 18 Nov 2018 00:46:00 +0000</pubDate><guid>https://new.halfrost.com/zh/tls_1-3_0-rtt/</guid><description>如 第2.3节 和 附录 E.5 所述，TLS不为 0-RTT 数据提供固有的重放保护。有两个潜在的威胁值得关注:
通过简单地复制 0-RTT 数据并发送来进行重放攻击的网络攻击者
网络攻击者利用 Client 重试行为使 Server 接收多个应用程序消息的副本。这种威胁在某种程度上已经存在，因为重视健壮性的 Client 通过尝试重试请求来响应网络错误。但是，0-RTT 为任何不维护全局一致服务器状态的 Server 系统添加了额外的维度。具体来说，如果服务器系统有多个 zone，zone B 中不接受来自 zone A 的 ticket，则攻击者可以将 A 中的 ClientHello 和 early data 复制到 A 和 B。对于 A，数据将在 0-RTT 内被接收，但对于 B，Server 将拒绝 0-RTT 数据，而是强制进行完全握手。如果攻击者 block 了 A 的 ServerHello，那么 Client 将会与 B 完成握手并且很可能重试一次请求，从而导致整个服务器系统上出现重复。
通过共享状态可以防止第一类攻击，以保证最多接受一次 0-RTT 数据。Server 应该通过实现本文中描述的方法之一或通过等效方法提供一定级别的重放安全性。但是，据了解，由于操作问题，并非所有部署都将维持该级别的状态。因此，在正常操作中，Client 并不知道这些 Server 实际实现了哪些机制(如果有的话)，因此必须只发送他们认为可以安全重放的 early data。
除了重放的直接影响之外，还存在一类攻击，即使通常被认为是幂等的操作也会被大量重放(定时攻击，资源限制耗尽等，如 附录 E.</description></item><item><title>TLS 1.3 Cryptographic Computations</title><link>https://new.halfrost.com/zh/tls_1-3_cryptographic_computations/</link><pubDate>Sun, 11 Nov 2018 00:44:00 +0000</pubDate><guid>https://new.halfrost.com/zh/tls_1-3_cryptographic_computations/</guid><description>TLS 握手建立一个或多个输入的 secrets，如下文所述，将这些 secrets 组合起来以创建实际工作密钥材料。密钥派生过程包含输入 secrets 和握手记录。请注意，由于握手记录包含来自 Hello 消息的随机值，因此即使使用相同的输入 secrets，任何给定的握手都将具有不同的流量 secrets，就像将相同的 PSK 用于多个连接的情况一样。
一. Key Schedule 密钥派生过程使用 HKDF [RFC5869] 定义的 HKDF-Extract 和 HKDF-Expand 函数，以及下面定义的函数:
HKDF-Expand-Label(Secret, Label, Context, Length) = HKDF-Expand(Secret, HkdfLabel, Length) Where HkdfLabel is specified as: struct { uint16 length = Length; opaque label&amp;lt;7..255&amp;gt; = &amp;#34;tls13 &amp;#34; + Label; opaque context&amp;lt;0..255&amp;gt; = Context; } HkdfLabel; Derive-Secret(Secret, Label, Messages) = HKDF-Expand-Label(Secret, Label, Transcript-Hash(Messages), Hash.length) Transcript-Hash 和 HKDF 使用的 Hash 函数是密码套件哈希算法。Hash.length 是其输出长度(以字节为单位)。消息是表示的握手消息的串联，包括握手消息类型和长度字段，但不包括记录层头。请注意，在某些情况下，零长度 context（由 &amp;quot;&amp;rdquo; 表示）传递给 HKDF-Expand-Label。本文档中指定的 labels 都是 ASCII 字符串，不包括尾随 NUL 字节。</description></item><item><title>TLS 1.3 Alert Protocol</title><link>https://new.halfrost.com/zh/tls_1-3_alert_protocol/</link><pubDate>Sun, 04 Nov 2018 00:35:00 +0000</pubDate><guid>https://new.halfrost.com/zh/tls_1-3_alert_protocol/</guid><description>TLS 提供 alert 内容类型用来表示关闭信息和错误。与其他消息一样，alert 消息也会根据当前连接状态的进行加密。
Alert 消息传达警报的描述以及在先前版本的 TLS 中传达消息严重性级别的遗留字段。警报分为两类：关闭警报和错误警报。在 TLS 1.3 中，错误的严重性隐含在正在发送的警报类型中，并且可以安全地忽略 &amp;ldquo;level&amp;rdquo; 字段。&amp;ldquo;close_notify&amp;rdquo; alert 用于表示连接从一个方向开始有序的关闭。收到这样的警报后，TLS 实现方应该表明应用程序的数据结束。
错误警报表示关闭连接中断(参见第6.2节)。收到错误警报后，TLS 实现方应该向应用程序表示出现了错误，并且不允许在连接上发送或接收任何其他数据。Server 和 Client 必须忘记在失败的连接中建立的秘密值和密钥，但是与会话 ticket 关联的 PSK 除外，如果可能，应该丢弃它们。
第 6.2 节中列出的所有警报必须与 AlertLevel = fatal 一起发送，并且在收到时必须将其视为错误警报，而不管消息中的 AlertLevel 如何。未知的警报类型必须都应该被视为错误警报。
注意：TLS 定义了两个通用警报(请参阅第6节)，以便在解析消息失败时使用。对端接收到了语法无法解析的消息(例如，消息具有超出消息边界的长度或包含超出范围的长度)必须以 &amp;ldquo;decode_error&amp;rdquo; 警报终止连接。对端接收到了语法正确，但是语义无效的消息(例如，p-1 的 DHE 共享或无效的枚举)必须使用 &amp;ldquo;illegal_parameter&amp;rdquo; 警报终止连接。
enum { warning(1), fatal(2), (255) } AlertLevel; enum { close_notify(0), unexpected_message(10), bad_record_mac(20), record_overflow(22), handshake_failure(40), bad_certificate(42), unsupported_certificate(43), certificate_revoked(44), certificate_expired(45), certificate_unknown(46), illegal_parameter(47), unknown_ca(48), access_denied(49), decode_error(50), decrypt_error(51), protocol_version(70), insufficient_security(71), internal_error(80), inappropriate_fallback(86), user_canceled(90), missing_extension(109), unsupported_extension(110), unrecognized_name(112), bad_certificate_status_response(113), unknown_psk_identity(115), certificate_required(116), no_application_protocol(120), (255) } AlertDescription; struct { AlertLevel level; AlertDescription description; } Alert; 一.</description></item><item><title>TLS 1.3 Record Protocol</title><link>https://new.halfrost.com/zh/tls_1-3_record_protocol/</link><pubDate>Sun, 28 Oct 2018 00:22:00 +0000</pubDate><guid>https://new.halfrost.com/zh/tls_1-3_record_protocol/</guid><description>TLS 记录协议接收要传输的消息，将数据分段为可管理的块，保护记录并传输结果。收到的数据经过验证，解密，重新组装，然后交付给更上层的协议。
TLS 记录允许同一层记录层上复用多个更高层的协议。本文档指定了 4 种内容类型：handshake，application_data，alert 和 change_cipher_spec。change_cipher_spec 记录仅用于兼容性目的。
实现方可能会在发送或接收第一个 ClientHello 消息之后，和，在接收到对等方的 Finished 消息之前的任何时间接收由单字节值 0x01 组成的未加密的类型 change_cipher_spec 的记录，如果接收到了这种记录，则必须简单地丢弃它而不进行进一步处理。请注意，此记录可能出现在握手中，这时候实现方是期望保护记录的，因此有必要在尝试对记录进行去除保护之前检测此情况。接收到任何其他 change_cipher_spec 值或接收到受保护的 change_cipher_spec 记录的实现方必须使用 &amp;ldquo;unexpected_message&amp;rdquo; alert 消息中止握手。如果实现方检测到在第一个 ClientHello 消息之前或在对等方的 Finished 消息之后收到的 change_cipher_spec 记录，则必须将其视为意外记录类型(尽管无状态的 Server 可能无法将这些情况与允许的情况区分开)
除非协商了某些扩展，否则实现方绝不能发送本文档中未定义的记录类型。如果 TLS 实现方收到意外的记录类型，它必须使用 &amp;ldquo;unexpected_message&amp;rdquo; alert 消息终止连接。新记录内容类型值由 IANA 在 TLS ContentType 注册表中分配，具体见第 11 节。
一. Record Layer 记录层将信息块分段为 TLSPlaintext 记录，TLSPlaintext 中包含 2^14 字节或更少字节块的数据。根据底层 ContentType 的不同，消息边界的处理方式也不同。任何未来的新增的内容类型必须指定适当的规则。请注意，这些规则比 TLS 1.2 中强制执行的规则更加严格。
握手消息可以合并为单个 TLSPlaintext 记录，或者在几个记录中分段，前提是：
握手消息不得与其他记录类型交错。也就是说，如果握手消息被分成两个或多个记录，则它们之间不能有任何其他记录。
握手消息绝不能跨越密钥更改。实现方必须验证密钥更改之前的所有消息是否与记录边界对齐; 如果没有，那么他们必须用 &amp;ldquo;unexpected_message&amp;rdquo; alert 消息终止连接。因为 ClientHello，EndOfEarlyData，ServerHello，Finished 和 KeyUpdate 消息可以在密钥更改之前立即发生，所以实现方必须将这些消息与记录边界对齐。</description></item><item><title>TLS 1.3 Handshake Protocol</title><link>https://new.halfrost.com/zh/tls_1-3_handshake_protocol/</link><pubDate>Sun, 21 Oct 2018 00:15:00 +0000</pubDate><guid>https://new.halfrost.com/zh/tls_1-3_handshake_protocol/</guid><description>握手协议用于协商连接的安全参数。握手消息被提供给 TLS 记录层，在记录层它们被封装到一个或多个 TLSPlaintext 或 TLSCiphertext 中，它们按照当前活动连接状态进行处理和传输。
enum { client_hello(1), server_hello(2), new_session_ticket(4), end_of_early_data(5), encrypted_extensions(8), certificate(11), certificate_request(13), certificate_verify(15), finished(20), key_update(24), message_hash(254), (255) } HandshakeType; struct { HandshakeType msg_type; /* handshake type */ uint24 length; /* remaining bytes in message */ select (Handshake.msg_type) { case client_hello: ClientHello; case server_hello: ServerHello; case end_of_early_data: EndOfEarlyData; case encrypted_extensions: EncryptedExtensions; case certificate_request: CertificateRequest; case certificate: Certificate; case certificate_verify: CertificateVerify; case finished: Finished; case new_session_ticket: NewSessionTicket; case key_update: KeyUpdate; }; } Handshake; 协议消息必须按照一定顺序发送(顺序见下文)。如果对端发现收到的握手消息顺序不对，必须使用 “unexpected_message” alert 消息来中止握手。</description></item><item><title>TLS 1.3 Introduction</title><link>https://new.halfrost.com/zh/tls_1-3_introduction/</link><pubDate>Sat, 13 Oct 2018 23:58:00 +0000</pubDate><guid>https://new.halfrost.com/zh/tls_1-3_introduction/</guid><description>一、TLS 协议的目的 TLS 的主要目标是为通信的双方提供一个安全的通道。对下层传输的唯一要求是一个可靠的有序的数据流。
认证： 通道的 Server 端应该总是被认证的；Client 端是可选的被认证。认证可以通过非对称算法（例如，RSA, 椭圆曲线数字签名算法(ECDSA)，或 Edwards 曲线数字签名算法(EdDSA)）完成，或通过一个对称的预共享密钥（PSK)。
机密性：在建立完成的通道上发送的数据只能对终端是可见的。TLS 协议并不能隐藏它传输的数据的长度，但是终端能够通过填充 TLS 记录来隐藏长度，以此来提升针对流量分析技术的防护。
完整性：在建立完成的通道上面发送数据，不可能存在数据被篡改还没有发现的情况。即数据一旦被修改，对端会立即发现这个篡改。
以上 3 点是必须要保证的，即使网络攻击者已经完全掌握了网络，发生了 RFC 3552 中发生的情况。关于 TLS 安全问题，下面有单独的文章专门再讨论。
二、TLS 协议的组成 TLS 协议主要由 2 大组件组成：
握手协议
握手协议主要需要处理在通信双方之间进行认证的所有流程。包括密钥协商，参数协商，建立共享密钥。握手协议被设计用来抵抗篡改；如果连接未受到攻击，则活动攻击者不应该强制对等方协商不同的参数。
记录协议
使用由握手协议建立的参数来保护通信双方的流量。记录协议将流量分为一系列记录，每个记录独立地使用密钥来保护机密性。
TLS 是一个独立的协议；高层协议可以透明地位于 TLS 之上。然而，TLS 标准并未指定协议如何增强 TLS 的安全，如何发起 TLS 握手以及如何理解认证证书交换，这些都留给运行在 TLS 之上的协议的设计者和实现者来判断。
本文档定义了 TLS 1.3 版。虽然 TLS 1.3 不是直接的与之前的版本兼容，所有版本的TLS都包含一个版本控制机制，即允许客户端和服务器通过协商，选出通信过程中采用的 TLS 版本。</description></item><item><title>秘密的实质——密钥</title><link>https://new.halfrost.com/zh/cipherkey/</link><pubDate>Sun, 07 Oct 2018 05:04:00 +0000</pubDate><guid>https://new.halfrost.com/zh/cipherkey/</guid><description>一、为什么需要密钥？ 密码的本质就是将较长的秘密——消息——变成较短的秘密——密钥。
—————布鲁斯 ● 施耐尔《网络信息安全的真相》
在前面几篇文章中，我们知道对称密码，公钥密码，消息认证码，数字签名，公钥证书，这些密码技术都需要一个密钥。密钥保护了信息的机密性。密钥最重要的是密钥空间的大下。密钥的长度决定了密钥空间的大小。密钥空间越大，暴力破解越困难。
二、什么是密钥？ 密钥仅仅是一个比特序列，但是它所具有的价值和明文等价。密钥的种类主要分为以下几种：
1. 对称密码的密钥和公钥密码的密钥 在对称加密中，加密和解密都用同一个密钥，也被称为共享密钥密码。
在公钥密码中，加密和解密都是不同的密钥。用于加密且能公开的密钥称为公钥。用于解密且不能公开的密钥称为私钥。
2. 消息认证码的密钥和数字签名的密钥 在消息认证码中，发送者和接收者使用共享的密钥来进行认证。消息认证码只有持有合法密钥的人才能计算出来。通过对比消息认证码就可以识别消息是否被篡改或者伪装。
在数字签名中，签名的生成和验证使用不同的密钥。只有持有私钥的本人才能够生成签名，但由于验证签名使用的是公钥，因此任何人都能够验证签名。
3. 用于保密的密钥和用于认证的密钥 对称密钥和公钥密码的密钥都是用于确保机密性的密钥。如果不知道用于解密的合法密钥，就可以达到保密明文的效果。
消息认证码和数字签名所使用的密钥是用于认证的密钥。如果不知道合法的密钥，就无法篡改数据，也无法伪装。
4. 会话密钥和主密钥 在 HTTPS 中 TLS 握手中仅限于本次通信的一次性密钥，下次就不能使用了。这种每次通信只能使用一次的密钥叫会话密钥(session key)。
由于每次会话都会产生新的会话密钥，即使密钥被窃听了，也只会影响本次会话。如果每次都使用相同的密钥叫主密钥(master key)。
5. 用于加密内容的密钥和用于加密密钥的密钥 加密的对象是用户直接使用的信息(内容)，这个时候密钥被称为CEK(Contents Encrypting Key)。用于加密密钥的密钥被称为KEK(Key Encrypting Key)。
会话密钥被作为 CEK 使用。主密钥被作为 KEK 使用的。
三、生成、配送、更新密钥 1. 生成密钥 用随机数生成密钥。生成密钥的最好办法就是使用随机数。 用口令生成密钥。通常为了防止字典攻击，需要在口令上附加一串称为盐(salt)的随机数。这种方法称为基于口令的密码。 2. 配送密钥 事先共享密钥 使用密钥分配中心 使用公钥密码 Diffie-Hellman 密钥交换 3. 更新密钥 这种技术通常被用在共享密钥中。在共享密钥进行通信的过程中，定期(例如，每发送 1000 个字)改变密钥。当然，发送者和接收者改变的步调要一致。</description></item><item><title>无法预测的根源——随机数</title><link>https://new.halfrost.com/zh/random_number/</link><pubDate>Sun, 23 Sep 2018 04:30:01 +0000</pubDate><guid>https://new.halfrost.com/zh/random_number/</guid><description>一、为什么需要随机数？ 在之前文章说提到了好多密码学技术，在这些技术中，都会看见随机数的身影。
生成密钥 用于对称密码和消息认证码 生成公钥密码 用于生成公钥密码和数字签名 生成初始化向量 IV 用于分组密码中的 CBC、CFB、OFB 模式 生成 nonce 用于防御重放攻击和分组密码中的 CTR 模式 生成盐
用于基于口令密码的 PBE 等 用随机数的目的是为了提高密文的不可预测性，让攻击者无法一眼看穿。
二、什么是随机数？ 给随机数下一个严密的定义很难。只能从性质去区分一些随机数的种类。
随机性 —— 不存在统计学偏差，是完全杂乱的数列 不可预测性 —— 不能从过去的数列推测出下一个出现的数 不可重现性 —— 除非将数列本身保存下来，否则不能重现相同的数列 随机性 不可预测性 不可重现性 备注 生成器 弱伪随机数 ✅ ❌ ❌ 只具备随机性 不可用于密码技术❌ 伪随机数生成器 PRNG (Preudo Random Number Generator) 强伪随机数 ✅ ✅ ❌ 具备不可预测性 可用于密码技术✅ 密码学伪随机数生成器 CPRNG (Cryptography secure Preudo Random Number Generator) 真随机数 ✅ ✅ ✅ 具备不可重现性 可用于密码技术✅ 真随机数生成器 TRNG (True Random Number Generator) 密码技术上使用到的随机数至少要达到不可预测性这一等级，即至少是强伪随机数，最好是真随机数。</description></item><item><title>随处可见的公钥证书</title><link>https://new.halfrost.com/zh/digital_certificate/</link><pubDate>Sat, 15 Sep 2018 09:01:00 +0000</pubDate><guid>https://new.halfrost.com/zh/digital_certificate/</guid><description>一、为什么需要公钥证书？ 在上一篇文章中我们谈到了数字签名，数字签名可以识别篡改或者发送者身份是否被伪装，也就是验证消息的完整性，还可以对消息进行认证。还可以防止抵赖。看似一切完美，但是他的弱点也在他的优点上，数字签名需要用公钥来确认发送者的身份。
我们需要从一个没有被伪装的发送者那里得到没有被篡改的公钥才行，为了验证得到的公钥是否合法，必须使用公钥证书。证书是将公钥当做一条消息，由一个可信的第三方对其签名后所得到的公钥。
二、什么是公钥证书？ 公钥证书(Public-Key Certificate，PKC)记录着个人信息(姓名、组织、邮箱地址等个人信息)和个人公钥，并由认证机构(Certification Authority、Certifying Authority，CA)施加数字签名。公钥证书也简称为证书(certificate)。
三、公钥证书标准规范 目前使用最广泛的是 ITU(International Telecommunication Union，国际电信联盟) 和 ISO(International Organization for Standardization，国际标准化组织) 制定的 X.509 规范(RFC3280)。基本上大多程序都是遵循这种证书生成和交换的标准规范。
四、公钥证书应用场景 用一个例子来展现证书的应用场景。下图是 Alice 向 Bob 发送密文的场景，在生成密文时所使用的 Bob 的公钥是通过认证机构获取的。
上图中步骤 1 - 7 是按照顺序来标识的。针对特殊步骤增加一些说明：
第一步中，Bob 的密钥可以是自己生成的，也可以由认证机构代为生成。 第三步中，认证机构在拿到 Bob 的公钥以后会开始认证这个公钥是否是 Bob 的。有三种验证等级，Class 1 通过邮箱中的邮件进行确认本人身份；Class 2 通过第三方数据库来确认本人身份；Class 3 通过当面认证和身份来确认本人身份。等级越高，身份认证越严格。 第五步中，Alice 使用认证机构 Trent 的公钥对证书中的数字签名进行验证，如果验证成功，就确认了证书中所包含的公钥是 Bob 的。 第六步中，图上虽然标识的是“公钥加密”的方式，但实际上这一步用混合加密的方式也是可以的。 上图中的步骤 1、2、3、4 并不是每次都需要这样请求。1、2、3 步只需要在注册新的公钥的时候才会进行。第四步仅在第一次需要使用公钥密码的时候需要，之后保存到了电脑中，就不用每次都请求公钥了。
五、什么是公钥基础设施 PKI？ 公钥基础设施(Public-Key Infrastructure)是为了能够更有效的运用公钥而制定的一系列规范和规格的总称。英文缩写 PKI。
PKI 是一个总称，并非指某一个单独的规范或规格。RSA 公司制定的 PKCS(Public-Key Cryptography Standards，公钥密码标准)系列规范也是 PKI 的一种，互联网规格 RFC(Request for Comments)也是 PKI 的一种，X.</description></item><item><title>无处不在的数字签名</title><link>https://new.halfrost.com/zh/digital_signature/</link><pubDate>Sat, 08 Sep 2018 13:01:00 +0000</pubDate><guid>https://new.halfrost.com/zh/digital_signature/</guid><description>一、为什么需要数字签名？ 从上一篇文章里面我们知道，消息认证码可以识别篡改或者发送者身份是否被伪装，也就是验证消息的完整性，还可以对消息进行认证。但是消息认证码的缺陷就在于它的共享密钥上面。由于共享密钥的原因，导致无法防止抵赖。
数字签名就是为了解决抵赖的问题的。解决的方法就是让通信双方的共享密钥不同，从密钥上能区分出谁是谁。
二、什么是数字签名？ 数字签名相当于现实世界中的盖章、签名的功能在计算机世界中进行实现的技术。数字签名可以识别篡改、伪装、防止抵赖。
在数字签名中，有 2 种行为：
生成消息签名的行为 验证消息签名的行为 生成消息签名的人是由消息发送者完成的，也称为“对消息签名”。生成签名就是根据消息内容计算数字签名的值。
验证数字签名的人是第三方。第三方验证消息的来源是否属于发送者。验证结果可以是成功，也可以是失败。
数字签名对签名密钥和验证密钥进行了区分，使用验证密钥是无法生成签名的。签名密钥只能由签名人持有，而验证密钥则是任何需要验证签名的人都可以持有。
私钥 公钥 公钥密钥 接收者解密时使用 发送者加密时使用 数字签名 签名者生成签名时使用 验证者验证签名时使用 谁持有密钥？ 个人持有 只要需要，任何人都可以持有 严格的来说，RSA 算法中公钥加密和数字签名正好是完全相反的关系，但是在其他公钥算法中有可能和数字签名不是完全相反的关系。
在公钥算法中，公钥用来加密，私钥用来解密。
在数字签名中，公钥用来解密(验证签名)，私钥用来加密(生成签名)。
三、生成和验证数字签名 有两种生成和验证数字签名的方法：
直接对消息签名的方法 对消息的散列值签名的方法 1. 直接对消息签名的方法 2. 对消息的散列值签名的方法 比较上面 2 种方法，一般都会使用第 2 种方法。原因是因为第 1 种方法要对整个消息进行加密，而且是公钥密钥算法，非常耗时。利用简短的单向散列函数来替代消息本身。再进行加密(对消息进行签名)，会快很多。
对上面的 2 种方法，有一些共性的问题进行解释：
为什么加密以后的密文能够具备签名的意义？
数字签名是利用了 “没有私钥的人就无法生成使用该私钥所生成的密文” 这一性质来实现的。生成的密文并非是用于保证机密性，而是用于代表一种只有持有该密钥的人才能生成的信息。所以私钥产生的密文是一种认证符号(authenticator)。 上面方法 2 中消息没有加密就直接发送了，这样不就没法保证消息的机密性了么？</description></item><item><title>消息认证码是怎么一回事？</title><link>https://new.halfrost.com/zh/message_authentication_code/</link><pubDate>Sat, 01 Sep 2018 12:01:00 +0000</pubDate><guid>https://new.halfrost.com/zh/message_authentication_code/</guid><description>一、为什么需要消息认证码？ 还是举一个银行汇钱的例子：A 向 B 汇钱 100 万元。如果攻击者从中攻击，篡改这条消息，就可能变成 A 向攻击者汇钱 1000 万元。这里针对汇款消息，需要注意两个问题：消息的 “完整性” 和 “认证” 。
消息的完整性，就叫消息的一致性，这个可以用上一篇文章中讲的消息指纹来判断，通过对比单向散列函数的 hash 值来判断这条消息的完整性，有没有被篡改。
消息的认证，指的是，消息是否来自正确的发送者。如果确认汇款请求确实来自 A，就相当于对消息进行了认证，代表消息没有被伪装。
如果同时需要识别出篡改和伪装，即要确认消息的完整性，又要对消息进行认证，这种情况下就需要消息认证码。
二、什么是消息认证码？ 消息认证码(Message Authentication Code) 是一种确认完整性并进行认证的技术，简称 MAC。
使用消息认证码可以确认自己收到的消息是否就是发送者的本意，也就是说可以判断消息是否被篡改，是否有人伪装成发送者发送了这条消息。
消息认证码也是密码学家工具箱中的 6 大工具之一：对称密码、公钥密码、单向散列函数、消息认证码、数字签名和伪随机数生成器。
消息认证码的输入包括任意长度的消息和一个发送者与接收者之间的共享密钥。输出固定长度的数据，输出的数据就是 MAC 值。
消息认证码和单向散列函数的区别就在有没有这个共享密钥上了。所以消息认证码就是利用共享密钥进行认证的。消息认证码和单向散列函数的散列值一样，如果消息发生了 1 比特的变化，MAC 值也会发生变化。所以消息认证码正是用这个性质来进行完整性的。
所以消息认证码可以理解为消息认证码是一种与密钥相关联的单向散列函数。
三、消息认证码的使用步骤 如果银行之间汇款采用消息认证码，流程会如下：
大体流程和验证单向散列函数基本一致，只不过消息认证码需要共享密钥来解出 MAC 值。
不过消息认证码的共享密钥存在密钥配送问题。密钥在配送中不能被窃听者窃取。解决密钥配送问题需要用到上上篇文章中讲的公钥密码、Diffie-Hellman 密钥交换等其他安全的方式配送密钥。
四、消息认证码使用现状 SWIFT（Society for Worldwide Interbank Financial Telecommunications&amp;mdash;环球同业银行金融电讯协会) 是一个目的为国际银行间的交易保驾护航的协会。银行和银行间通过 SWIFT 来传递交易消息，SWIFT 会利用消息认证码校验消息的完整性和对消息的验证。消息认证码的共享密钥是由人进行配送的。
IPsec 是对 IP 协议增加安全性的一种方式，在 IPsec 中，对消息的认证和完整性校验也是用消息认证码的方式。
SSL/TLS 对通信内容的认证和完整性校验也用了消息认证码。</description></item><item><title>消息的“指纹”是什么？</title><link>https://new.halfrost.com/zh/one_way_hash/</link><pubDate>Sun, 26 Aug 2018 02:49:00 +0000</pubDate><guid>https://new.halfrost.com/zh/one_way_hash/</guid><description>一、为什么需要“指纹”？ 在警察破案的时候，会经常接触到犯人的指纹。指纹能从生物的角度上判断它是不是某一个人。这么看来，指纹相当于一个人独一无二的东西，通过他就能找到对应唯一的一个人。
在网络传输过程中，可能存在中间人。那人们就有了这样的想法：能不能找到消息的“指纹”？这样就可以知道消息是谁发送的，从而避免中间人的攻击。
二、单向散列函数 开发者日常工作中会使用 git 命令。git diff 命令可以比对 2 次 commit，展示出两者不同的地方。那么相同的文件不会展示。那么 git diff 是如何对比两个文件的呢？如果一个文件巨大，能否有一个简单快捷的方式能判断文件是否被更改了呢？
这个时候文件的“指纹”的作用就凸显出来了。从一一比对 2 个文件的每一行，到现在只需要比对文件的“指纹”信息，如果一个指纹信息就能检查文件完整性，该多么方便啊！
单向散列函数就是一种采集文件“指纹”的技术。单向散列函数生成的散列值，就相当于消息的“指纹”。单向散列函数(one-way hash function)有一个输入和一个输出，其中输入成为消息(message)，输出成为散列值(hash value)。单向散列函数可以根据消息的内容计算散列值，而散列值就可以被用来检查消息的完整性。
这个的消息可以是更加广义的消息，可以是图片，也可以是声音，单向散列函数并不需要关心消息实际代表的含义。在单向散列函数的眼里只有 0、1、0、1 比特流。
单向散列函数计算出来的散列值的长度和消息的长短没有关系。以 SHA-256 为例，它计算出来的散列值永远都是 256 比特(32 字节)。
单向散列函数有以下几点性质：
1. 根据任意长度的消息计算出固定长度的散列值 2. 能够快速计算出散列值 3. 消息不同，散列值也不同 最后一点性质是最关键的。如果 2 个文件不同，算出的散列值却是相同的，那么单向散列函数的意义也就不存在了。实际上，两个不同的消息产生同一个散列值的情况称为**“碰撞(collision)”**。散列函数需要确保在不人为干涉的情况下，不存在碰撞的情况。密码学中所使用的单向散列函数，都需要具备抗碰撞性质。
单向散列函数必须要确保要找到和该条消息具有相同散列值的另外一条消息是非常困难的，这一性质称为弱抗碰撞性，单向散列函数都必须具备弱抗碰撞性。
要找到具有相同散列值但互不相同的两条消息是非常困难的，这一性质称为强抗碰撞性。这里的散列值可以是任意值。
密码技术中所使用的单向散列函数，不仅要具备弱抗碰撞性，还必须具备强抗碰撞性。
这里的弱抗碰撞性和强抗碰撞性是相对的概念，并不是说“很弱而不具备抗碰撞性”！
4. 单向性 单向散列函数必须具备单向性，单向性指的是无法通过散列值反算出消息的性质。
如上图所示，反算出消息的路是不通的。这也就是单向散列函数，单向两个字的来源。需要特别注意的一点是，单向散列函数并不是一种加密，所以它是无法通过解密的方法得到原消息的。
单向散列函数也称为消息摘要函数(message digest function)、哈希函数、杂凑函数。输入单向函数的消息也称为原像(pre-image)。单向散列函数输出的散列值也称为消息摘要(message digest)或者指纹(fingerprint)。完整性也被称为一致性。
对于攻击者来说，Hash 算法的破解难度是，强抗碰撞性&amp;lt;弱抗碰撞性&amp;lt;单向性。
三、单向散列函数的实际应用 检测软件是否被篡改 基于口令的加密 单向散列函数也被用于基于口令的加密(Password Based Encryption，PBE)。PBE 的原理是将口令和盐(salt，通过伪随机数生成器产生的随机值)混合后计算其散列值，然后将这个散列值用作加密的密钥。通过这样的方法能够防御针对口令的字典攻击。</description></item><item><title>翱游公钥密码算法</title><link>https://new.halfrost.com/zh/asymmetric_encryption/</link><pubDate>Sun, 19 Aug 2018 00:22:00 +0000</pubDate><guid>https://new.halfrost.com/zh/asymmetric_encryption/</guid><description>一、引子 在对称加密中，例如一次性密码本，就存在密钥配送的问题。在 DES、AES 中也存在这个问题。由于加密和解密的密钥是相同的，所以必须向接收者配送密钥。如果使用公钥密钥，则无需向接收者配送用于解密的密钥，这样就解决了密钥配送的问题，可以说公钥密码是密码学历史上最伟大的发明。
二、配送密钥问题 为了防止中间人截获密钥，安全的把密钥传递给通信对方。有以下 4 种方式：
1. 事先共享密钥 这种方法虽然有效，但是具有局限性。在一次性密码本中，我们说过，大国之间的热线是用这种方式加密的，但是密钥是靠特工押送过去的。如果通讯对方在附近，提前共享密钥还比较方便。如果通讯对方在世界各地，这种方式也就存在局限性了。
另外通讯量增大以后，密钥个数也会陡增。n 个人两两通讯，需要 n * (n-1) /2 个密钥。这点来看，也不现实。
2. 密钥分配中心 为了解决事先共享密钥的密钥增多的问题。于是有人想出了密钥分配中心(Key Distribution Center, KDC)的办法。每次加密的密钥由密钥中心进行分配，每个人只要和密钥中心事先共享密钥就可以了。
虽然这个方法解决了密钥增多的问题，但是又带来了新的问题。
密钥中心存储和记录了所有的密钥，一旦它出现故障或者被攻击破坏，那么所有的加密都会瘫痪。这也是集中式管理的缺点。
3. Diffie-Hellman 密钥交换 为了解决集中式管理的缺点，那么应该密钥的配送还是不能用集中式。于是有人想出了 Diffie-Hellman 密钥交换的方法。
在 Diffie-Hellman 密钥交换中，加密通信双方需要交换一些信息，而这些信息即便被窃听者窃听，也不会有任何问题。
根据交换的信息，双方各自生成相同的密钥。而窃听者无法生成相同的密钥。这种方式可行。不过这种方式不算是非对称加密，在本文中不详细讨论。
4. 公钥密码 非对称加密有一个公钥和一个私钥。公钥可以在网上传播，被窃听者拿到也没有关系，由于没有私钥，他也无法解开密文。私钥只要掌握在接收者手上就不会导致密文被窃听。
举个例子：超市里面的存包处，所有顾客有硬币就可以存包。硬币就是“公钥”，顾客把包放进箱子里，（明文加密），箱子锁上以后就没人能打开。这个时候窃听者也拿不走存进去的包。这个明文（包），只有私钥才能打开。客户存完包以后会生成一个私钥，只要这个钥匙在手，就可以随时开箱拿包。
三、非对称加密 非对称加密一般指的是具有公钥密钥(public-key cryptography)的加密算法。密钥分为加密密钥和解密密钥两种。发送者用加密密钥对信息进行加密，接收者用解密密钥对密文进行解密。可以公开出去的叫公钥(public key)，保存在自己手上不公开的叫私钥(private key)。
公钥和私钥是一一对应的。一对公钥和私钥统称为密钥对(key pair)。在数学的关系上，这两者不能单独生成。
四、非对称加密存在的问题 公钥密码虽然解决了密钥配送的问题，但是并不意味着它解决了所有问题。公钥密码存在以下几个问题：
公钥认证 处理速度不到对称加密的十分之一 五、RSA 算法流程 RSA 是一种公钥密码算法，它的名字是由它的三位开发者，即 Ron Rivest、Adi Shamir 和 Leonard Adleman 的姓氏的首字母组成的 (Rivest-Shamir-Adleman)。1983 年，RSA 公司为 RSA 算法在美国取得了权利，但是现在该专利已经过期了。
RSA 可以被用于公钥密码和数字签名。</description></item><item><title>漫游对称加密算法</title><link>https://new.halfrost.com/zh/symmetric_encryption/</link><pubDate>Sat, 11 Aug 2018 17:06:00 +0000</pubDate><guid>https://new.halfrost.com/zh/symmetric_encryption/</guid><description>一、引子 在引出对称加密之前，有必要先介绍一种位运算，XOR。XOR 的全称是 exclusive or，中文翻译是异或。
0 XOR 0 = 0 0 XOR 1 = 1 1 XOR 0 = 1 1 XOR 1 = 0 XOR 可以看成是“两个数相同，异或为 0 ，不同则异或为 1”。
异或也叫半加运算，其运算法则相当于不带进位的二进制加法：二进制下用1表示真，0表示假，则异或的运算法则为：
0 ⊕ 0 = 0 1 ⊕ 0 = 1 0 ⊕ 1 = 1 1 ⊕ 1 = 0 这些法则与加法是相同的，只是不带进位，所以异或常被认作不进位加法。由异或的这种特点，也就引出了它的一个常用特性，两个相同的数进行 XOR 运算的结果一定为 0。
对应的，我们也可以得到如下的运算法则：
1. a ⊕ a = 0 2. a ⊕ b = b ⊕ a 交换率 3.</description></item><item><title>密码学概述</title><link>https://new.halfrost.com/zh/cryptography_overview/</link><pubDate>Sat, 04 Aug 2018 17:01:00 +0000</pubDate><guid>https://new.halfrost.com/zh/cryptography_overview/</guid><description>一、为什么需要加密 每个人都有自己的秘密，如果不加密，在网上传输很容易被监听。如果涉及到金钱相关，密码泄露以后很容易造成损失。所以都会利用加密 cryptography 技术，保证信息的机密性 confidentiality。
信息被加密以后变成了密文在网上传播，接收者拿到密文进行解密 cryptanalysis，解密以后就可以看到明文。
进行破译密码的人叫破译者，破译者不一定都是坏人，密码学研究者为了研究密码强度，也会需要对密码进行破译，研究者在这种情况下也是破译者。
加密与压缩的顺序？
压缩一定在加密之前。因为加密以后，比特序列的冗余性消失，基本上无法再压缩了。在加密前进行压缩的做法不仅仅限于混合密码系统，而是对所有密码都适用。
二、对称加密 对称密码 (symmetric cryptography)是指在加密和解密时使用同一密钥的方式。对应的加密方式是对称加密。目前广泛使用 AES。
对称密码有多种别名，公共密钥密码(common-key cryptography)，传统密码(conventional cryptography)，私钥密码(secret-key cryptography)，共享密钥密码(shared-key cryptography)等。
对称密码需要解决将解密密钥配送给接收者的密钥配送问题。
三、非对称加密 公钥密码 (public-key cryptography)则是指在加密和解密时使用不同密钥的方式。对应的加密方式是非对称加密。目前广泛使用 RSA。(RSA、ElGamal、Rabin、DH、ECDH)
公钥密码解决了密钥配送的问题，但是存在通过中间人攻击被伪装的风险，因此需要对带有数字签名的公钥进行认证。
四、单向散列函数 网上很多免费的软件，为了防止软件被篡改，有安全意识的软件发布者会在发布软件的同时会发布这个版本软件的散列值 hash。散列值是用单向散列函数(one-way hash function)计算出来的数值。目前广泛使用 SHA-2(SHA-224、SHA-356、SHA-384、SHA-512) 和 具有全新结构的 SHA-3(Keccak 算法)
散列值 hash，又被称为哈希值，密码校验和(cryptographic checksum)，指纹(fingerprint)，消息摘要(message digest)。
单向散列函数并不是为了保证消息的机密性，而是完整性(integrity)。完整性指的是，数据是正确的，而不是伪造的。单向散列函数是保证信息的完整性的密码技术，它会检测数据是否被篡改。
单向散列函数可以单独使用，也可以作为消息认证码、数字签名以及伪随机数生成器等技术的组成元素使用。
五、消息认证码 为了确认消息是否来自期望的通信对象，可以通过使用消息认证码(message authentication code)。消息认证码主要是提供了认证(authentication)机制，与此同时它也能保证消息的完整性。
消息认证码中最常用的单向散列函数是 HMAC。HMAC 的构成不依赖于某一种具体的单向散列函数算法。
消息认证码能对通信对象进行认证，但无法对第三方进行认证。它也无法防止否认。消息认证码可以用来实现认证加密。
六、数字签名 试想有这样一种情况，A 欠 B 100 万美刀，于是 A 向 B 打了一张欠条。一周以后，A 拿到钱以后就不承认那张欠条是自己写的，抵赖借钱了。
这个时候就牵扯到密码学里面的防抵赖的技术 —— 数字签名。数字签名类似现实世界中的签名和盖章，数字签名是一种能防止用户抵赖，伪装，篡改和否认的密码技术。目前广泛使用的数字签名算法包括 RSA、ElGamal、DSA、椭圆曲线 DSA(ECDSA)、爱德华兹曲线 DSA(EDDSA)等。</description></item><item><title>本站开始支持 QUIC</title><link>https://new.halfrost.com/zh/quic_start/</link><pubDate>Sun, 22 Jul 2018 00:33:00 +0000</pubDate><guid>https://new.halfrost.com/zh/quic_start/</guid><description>一. QUIC 是什么 QUIC 是 Quick UDP Internet Connections 的缩写，谷歌发明的新传输协议。与 TCP 相比，QUIC 可以减少延迟。从表面上看，QUIC 非常类似于在 UDP 上实现的 TCP + TLS + HTTP/2。由于 TCP 是在操作系统内核和中间件固件中实现的，因此对 TCP 进行重大更改几乎是不可能的。但是，由于 QUIC 建立在 UDP 之上，因此没有这种限制。QUIC 可以实现可靠传输，而且相比于 TCP，它的流控功能在用户空间而不在内核空间，那么使用者就 不受限于 CUBIC 或是 BBR，而是可以自由选择，甚至根据应用场景自由调整优化。
QUIC 与现有 TCP + TLS + HTTP/2 方案相比，有以下几点主要特征：
利用缓存，显著减少连接建立时间 改善拥塞控制，拥塞控制从内核空间到用户空间 没有 head of line 阻塞的多路复用 前向纠错，减少重传 连接平滑迁移，网络状态的变更不会影响连接断线。 QUIC 在 UDP 之上，如果想要和 TCP/IP 体系类比，那么就是上图。QUIC 可以类比 TCP/IP 中的 TLS 一层。但是功能又不完全是 TLS ，还有一部分 HTTP/2 ，下面还包括一部分 TCP 的功能，比如 拥塞控制、丢包恢复、流量控制等特性。</description></item><item><title>本站开始支持 TLS 1.3</title><link>https://new.halfrost.com/zh/tls1-3_start/</link><pubDate>Sat, 14 Jul 2018 15:32:00 +0000</pubDate><guid>https://new.halfrost.com/zh/tls1-3_start/</guid><description>笔者最近在研究 TLS 1.3 协议，所以想要实践一下，博客必须先部署 TLS 1.3 。另外它的 0-RTT 对博客速度的也是一种提升。需要注意目前 Chrome 68 和 Firefox 支持的是 TLS 1.3 draft 28，暂时不要用在生产环境。
关于 TLS 1.3 的更多细节，会在之后的文章里面分析。
一. 安装依赖 先来看看依赖项的一些情况。
Nginx 1.13.0 开始 正式支持 ssl_protocols 的 TLSv1.3 的选项. 在此之前 TLSv1.2 选项会自动使用 TLSv1.3.
OpenSSL 目前有 draft-18 分支, pre2 版本的 draft-23 以及 pre7+ 的 draft-28 通过 tls1.h (include/openssl/tls1.h 第35行) 可以查看目前的 Draft。
从 Chrome 65 开始会默认开启并使用 TLSv1.3 Draft 23, 从 Chrome 68 开始支持 Draft 28 (Firefox Nightly 应该也支持).</description></item><item><title>博客跑分优化</title><link>https://new.halfrost.com/zh/ghost_fast/</link><pubDate>Sat, 07 Jul 2018 15:46:00 +0000</pubDate><guid>https://new.halfrost.com/zh/ghost_fast/</guid><description>本篇文章会持续更新，因为优化无止境。
本文会罗列目前已经优化过的点。从开始页面略卡顿，到最后跑满分，笔者在这里都会一一展现出来。
起点 博客页面优化从 Chrome 的 Performance 开始。
从上图中可以看到页面前 550ms 都是空白，时间耗在了加载 JS 的过程上了。这段是 Disqus 的脚本和 Google 统计脚本。
笔者优化的原则是：必须要加载的 pre-load，不必须加载的并且未出现在屏幕里面的 lazy-load。
优化的思路很简单，在 DOM 渲染之前，去掉上图中大长条的黄色的矩形，这段时间是加载 JS 的时间，阻塞页面渲染了。笔者在页面渲染的时候不会加载 Disqus 脚本，只在博文页面用的时候，点击按钮的时候才会 lazy-load 加载脚本。
至于 Google 统计脚本，需要用最新的 gtag 的异步版本，并且加上 async 标志就可以解决。当然如果想彻底异步这段统计的代码，需要服务端做一些改造。
这里可以参考这两篇文章《优化 Google Analytics 异步加载来提高你的网站速度》、《服务端使用 Google Analytics》。
如果还有百度统计的脚本的话，将 baidu 统计生成的代码移入网站，将 type 设置为 test 等非 mime/type 类型，这样浏览器便不会解析这段 script，baidu 统计却也能校验通过：
&amp;lt;script type=&amp;#34;text/test&amp;#34;&amp;gt; var _hmt = _hmt || []; (function() { var hm = document.createElement(&amp;#34;script&amp;#34;); hm.src = &amp;#34;https://hm.baidu.com/hm.js?XXXX&amp;#34;; var s = document.</description></item><item><title>Ghost 博客炫技"新"玩法</title><link>https://new.halfrost.com/zh/ghost_feature/</link><pubDate>Sat, 30 Jun 2018 08:55:00 +0000</pubDate><guid>https://new.halfrost.com/zh/ghost_feature/</guid><description>由于 Ghost 升级到了最新版本，新增加了很多玩法。这里罗列一下“新”玩法，也是对笔者博客新功能的说明。
如果读者也想修改自己的主题，那么请先阅读官方的文档：
ghost themes
ghost api
Ghost 添加导航页面 之前老版本的博客没有导航功能，也有读者问过这个事情，这次既然升级了，首先完成这个需求。
新版本的博客导航功能放到了 footer 上了。可能有人问为何不放在 header 上。我尝试了好几种位置，都觉得不太合适，还是放在 footer 上了。
footer 上主要分了 3 列，TAGS、ABOUT、NEWSLETTER。
一般博客都会有标签，归档，友链，关于，订阅。这次一口气都加全了。
给每篇文章添加封面 由于受到了知乎专栏的 CSS 影响，加上 Ghost 本身也支持 cover 功能，所以笔者在这次升级的时候，把原来的主题重新修改了，把 cover 封面重新添加回来了。
用过知乎专栏的朋友会知道，每篇文章一进去，就是一个占满全屏的封面题图，这种设计笔者觉得挺好的，所以也“抄”过来了。
其他的设计也说一下，题目标题上面是文章对应的 tag。文章下面有预估阅读时间，这个是根据人类平均阅读时间每分钟 100 个字算出来的。右下角是文章作者的名字和头像，以及文章发布时间。
最上一排增加了 3 个 iconfont 的按钮，返回按钮是为了 PWA 特意增加的，不然不能返回到主页了。搜索和侧边栏也都是这次增加的新功能，下面会细说。
文章添加目录 这个功能也是读者提过的需求。因为有些文章比较长，常常容易看到后面忘记了前面，如果有一个目录，可以分章节阅读文章，也可以迅速浏览目录，抓住文章脉络。这次升级笔者也加上了这个功能。
文章目录这个功能的不难，只要遍历一遍 markdown 的标题就可以了。github 上可以找到现成的 jquery 版本的代码 jquery.toc.js。引用它，然后初始化的时候加上自己的配置：
//初始化 toc 插件 $(&amp;#39;#toc&amp;#39;).initTOC({ selector: &amp;#34;h1, h2, h3, h4, h5, h6&amp;#34;, scope: &amp;#34;article&amp;#34;, overwrite: false, prefix: &amp;#34;toc&amp;#34; }); selector 代表会搜索 markdown 文章标题的层级深度，这里是从 h1 - h6 。scope 表示的是 toc 搜索范围，由于在 ghost 中文章是在 &amp;lt;article&amp;gt; 标签里面的，所以这里 scope 为 article。</description></item><item><title>Ghost 博客升级指南</title><link>https://new.halfrost.com/zh/ghost_update/</link><pubDate>Sat, 23 Jun 2018 02:58:00 +0000</pubDate><guid>https://new.halfrost.com/zh/ghost_update/</guid><description>在笔者一开始建站的时候，用的 Ghost 版本就是 0.7.4 中文版。这个非常早期的版本也有不少 hack 的玩法。直到最近周围有朋友也在玩 Ghost 的时候，我发现最新版很多新功能非常吸引我：比如最新版早就支持了 markdown 插入表格，也能支持 LateX。关于不支持 markdown 插入表格这个比较痛苦，之前表格的替代方法是在 github 上发布完文章以后截图，然后把图片传到 Ghost 上。
既然几年过去了，要升级就直接升级到最新版吧。截止到这篇文章的时间，当前最新版是 Ghost 1.24.8 。接下来写一些升级指南，如果也有和我一样用 Ghost 0.7.4 中文版的想升级到最新版，可以看看笔者的升级之路。
准备工作 准备工作当前是备份老版本的配置和数据。这里列一个需要备份的清单：
数据库文件 通过 Ghost 后台管理系统，导出所有博文数据，其实就是从 MySQL 中导出的 JSON 文件。 主题文件 ghost/content/themes/ 服务器端 Ghost 的配置文件 config.js Nginx 下的 ghost.conf 配置文件 服务器上的 Ghost 整个文件夹 备份整个文件夹是可选的，是为了防止出错可以回退，丢数据可以找回。 开始升级 Ghost 在 V1.XX 以后改动比较大，因为加入了很多方便的脚手架工具，比如 Ghost、Ghost-CLI 等等。从 0.7.4 升级上来算是一次 breaking，所以 Ghost 这些工具都需要新装。
Ghost is a fully open source, hackable platform for building and running a modern online publication.</description></item><item><title>深入浅出 FlatBuffers 之 FlexBuffers</title><link>https://new.halfrost.com/zh/flatbuffers_flexbuffers/</link><pubDate>Sat, 16 Jun 2018 09:24:00 +0000</pubDate><guid>https://new.halfrost.com/zh/flatbuffers_flexbuffers/</guid><description>一. FlexBuffers 是什么？ FlexBuffers 是 FlatBuffers 中一个 schema-less 的版本。它和 FlatBuffers 其他类型编码原理一样，所有的数据也都是通过偏移量来访问的，所有的标量都按照它们自己的大小对齐，并且所有的数据总是以小端格式存储。
一个不同之处在于 FlexBuffers 是从前到后构建的，因此子项存储在父项之前，并且 root table 是从最后一个字节处开始。
另一个区别是标量数据的存储位数 bit 是可变的（8/16/32/64）。当前宽度总是由父级确定，即如果标量位于数组中，则数组一次确定所有元素的所占字节宽度。为特定的数组选择最小 bit 位宽是编码器自动执行的操作，因此通常不需要用户担心它，但是了解此功能(当然不会在一个每个元素大小都是一个字节的数组中插入一个 double)更有助于提高效率。
FlexBuffers 与 FlatBuffers 不同，它只有一种偏移量，它是一个无符号整数，用于表示自身地址（存储偏移量的地方）的负方向上的偏移的字节数。
二. 为什么要发明 FlexBuffers ？ 有时候需要存储一些不符合 schema 格式的数据，因为无法提前知道需要存储哪些内容。出于此目的，FlexBuffers 就诞生了。FlatBuffers 有一个称为 FlexBuffers 的专用格式。这是一种二进制格式，可以与 FlatBuffers 一起使用（通过以 FlexBuffers 格式存储缓冲区的一部分），或者也可以作为自己独立的序列化格式。
虽然失去了强大的类型功能，但仍然保留 FlatBuffers 在其他序列化格式（基于 schema 或不基于 schema）上最独特的优势：也可以在不解析/复制/分配对象的情况下访问 FlexBuffers。这在效率/内存友好性方面是一个优势，并且允许独特的用法，例如映射大量的自由格式数据。
FlexBuffers 的设计和实现是一种非常紧凑的编码，将字符串的自动合并与容器大小结合在一起，自动跳转大小（8/16/32/64位）。许多值和 offset 可以用 8 位编码。虽然没有 schema 的结构，由于需要具有自描述性，所以通常体积会变得更大一些，但与常规 FlatBuffers 相比，FlexBuffers 在许多情况下会生成更小的二进制文件。
需要注意的是，FlexBuffers 比普通的 FlatBuffers 还要慢，所以我们建议只在需要时使用它。
三. FlexBuffers 中的 Vectors 如何表示一个 vectors 是 FlexBuffers 编码的核心。</description></item><item><title>深入浅出 FlatBuffers 之 Encode</title><link>https://new.halfrost.com/zh/flatbuffers_encode/</link><pubDate>Sun, 10 Jun 2018 09:06:00 +0000</pubDate><guid>https://new.halfrost.com/zh/flatbuffers_encode/</guid><description>一. FlatBuffers 生成二进制流 FlatBuffers 的使用和 Protocol buffers 基本类似。只不过功能比 Protocol buffers 多了一个解析 JSON 的功能。
编写 schema 文件，描述数据结构和接口定义。 用 flatc 编译，生成相应语言的代码文件。 解析 JSON 数据，把数据存储成对应的 schema，并存入 FlatBuffers 二进制文件中。 使用 FlatBuffers 支持的语言（如C ++，Java等）生成的文件进行开发。 接下来简单的定义一个 schema 文件，来看看 FlatBuffers 的使用。
// Example IDL file for our monster's schema. namespace MyGame.Sample; enum Color:byte { Red = 0, Green, Blue = 2 } union Equipment { Weapon } // Optionally add more tables. struct Vec3 { x:float; y:float; z:float; } table Monster { pos:Vec3; // Struct.</description></item><item><title>深入浅出 FlatBuffers 之 Schema</title><link>https://new.halfrost.com/zh/flatbuffers_schema/</link><pubDate>Sun, 03 Jun 2018 09:02:00 +0000</pubDate><guid>https://new.halfrost.com/zh/flatbuffers_schema/</guid><description>一. FlatBuffers 是什么？ FlatBuffers 是一个序列化开源库，实现了与 Protocol Buffers，Thrift，Apache Avro，SBE 和 Cap&amp;rsquo;n Proto 类似的序列化格式，主要由 Wouter van Oortmerssen 编写，并由 Google 开源。Oortmerssen 最初为 Android 游戏和注重性能的应用而开发了FlatBuffers。现在它具有C ++，C＃，C，Go，Java，PHP，Python 和 JavaScript 的端口。
FlatBuffer 是一个二进制 buffer，它使用 offset 组织嵌套对象（struct，table，vectors，等），可以使数据像任何基于指针的数据结构一样，就地访问数据。然而 FlatBuffer 与大多数内存中的数据结构不同，它使用严格的对齐规则和字节顺序来确保 buffer 是跨平台的。此外，对于 table 对象，FlatBuffers 提供前向/后向兼容性和 optional 字段，以支持大多数格式的演变。
FlatBuffers 的主要目标是避免反序列化。这是通过定义二进制数据协议来实现的，一种将定义好的将数据转换为二进制数据的方法。由该协议创建的二进制结构可以 wire 发送，并且无需进一步处理即可读取。相比较而言，在传输 JSON 时，我们需要将数据转换为字符串，通过 wire 发送，解析字符串，并将其转换为本地对象。Flatbuffers 不需要这些操作。你用二进制装入数据，发送相同的二进制文件，并直接从二进制文件读取。
尽管 FlatBuffers 有自己的接口定义语言来定义要与之序列化的数据，但它也支持 Protocol Buffers 中的 .proto格式。
在 schema 中定义对象类型，然后可以将它们编译为 C++ 或 Java 等各种主流语言，以实现零开销读写。FlatBuffers 还支持将 JSON 数据动态地分析到 buffer 中。
除了解析效率以外，二进制格式还带来了另一个优势，数据的二进制表示通常更具有效率。我们可以使用 4 字节的 UInt 而不是 10 个字符来存储 10 位数字的整数。</description></item><item><title>高效的序列化/反序列化数据方式 Protobuf</title><link>https://new.halfrost.com/zh/protobuf_decode/</link><pubDate>Sun, 27 May 2018 16:44:00 +0000</pubDate><guid>https://new.halfrost.com/zh/protobuf_decode/</guid><description>一. protocol buffers 序列化 上篇文章中其实已经讲过了 encode 的过程，这篇文章以 golang 为例，从代码实现的层面讲讲序列化和反序列化的过程。
举个 go 使用 protobuf 进行数据序列化和反序列化的例子，本篇文章从这个例子开始。
先新建一个 example 的 message：
syntax = &amp;#34;proto2&amp;#34;; package example; enum FOO { X = 17; }; message Test { required string label = 1; optional int32 type = 2 [default=77]; repeated int64 reps = 3; optional group OptionalGroup = 4 { required string RequiredField = 5; } }利用 protoc-gen-go 生成对应的 get/ set 方法。代码中就可以用生成的代码进行序列化和反序列化了。
package main import ( &amp;#34;log&amp;#34; &amp;#34;github.</description></item><item><title>高效的数据压缩编码方式 Protobuf</title><link>https://new.halfrost.com/zh/protobuf_encode/</link><pubDate>Thu, 24 May 2018 16:25:00 +0000</pubDate><guid>https://new.halfrost.com/zh/protobuf_encode/</guid><description>一. protocol buffers 是什么？ Protocol buffers 是一种语言中立，平台无关，可扩展的序列化数据的格式，可用于通信协议，数据存储等。
Protocol buffers 在序列化数据方面，它是灵活的，高效的。相比于 XML 来说，Protocol buffers 更加小巧，更加快速，更加简单。一旦定义了要处理的数据的数据结构之后，就可以利用 Protocol buffers 的代码生成工具生成相关的代码。甚至可以在无需重新部署程序的情况下更新数据结构。只需使用 Protobuf 对数据结构进行一次描述，即可利用各种不同语言或从各种不同数据流中对你的结构化数据轻松读写。
Protocol buffers 很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。
二. 为什么要发明 protocol buffers ？ 大家可能会觉得 Google 发明 protocol buffers 是为了解决序列化速度的，其实真实的原因并不是这样的。
protocol buffers 最先开始是 google 用来解决索引服务器 request/response 协议的。没有 protocol buffers 之前，google 已经存在了一种 request/response 格式，用于手动处理 request/response 的编组和反编组。它也能支持多版本协议，不过代码比较丑陋：
if (version == 3) { ... } else if (version &amp;gt; 4) { if (version == 5) { ... } .</description></item><item><title>全双工通信的 WebSocket</title><link>https://new.halfrost.com/zh/websocket/</link><pubDate>Sat, 19 May 2018 15:30:00 +0000</pubDate><guid>https://new.halfrost.com/zh/websocket/</guid><description>一. WebSocket 是什么？ WebSocket 是一种网络通信协议。在 2009 年诞生，于 2011 年被 IETF 定为标准 RFC 6455 通信标准。并由 RFC7936 补充规范。WebSocket API 也被 W3C 定为标准。
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工(full-duplex)通讯的协议。没有了 Request 和 Response 的概念，两者地位完全平等，连接一旦建立，就建立了真•持久性连接，双方可以随时向对方发送数据。
(HTML5 是 HTML 最新版本，包含一些新的标签和全新的 API。HTTP 是一种协议，目前最新版本是 HTTP/2 ，所以 WebSocket 和 HTTP 有一些交集，两者相异的地方还是很多。两者交集的地方在 HTTP 握手阶段，握手成功后，数据就直接从 TCP 通道传输。)
二. 为什么要发明 WebSocket ？ 在没有 WebSocket 之前，Web 为了实现即时通信，有以下几种方案，最初的 polling ，到之后的 Long polling，最后的基于 streaming 方式，再到最后的 SSE，也是经历了几个不种的演进方式。
(1) 最开始的短轮询 Polling 阶段 这种方式下，是不适合获取实时信息的，客户端和服务器之间会一直进行连接，每隔一段时间就询问一次。客户端会轮询，有没有新消息。这种方式连接数会很多，一个接受，一个发送。而且每次发送请求都会有 HTTP 的 Header，会很耗流量，也会消耗 CPU 的利用率。</description></item><item><title>Machine Learning 机器学习笔记</title><link>https://new.halfrost.com/zh/machine_learning_contents/</link><pubDate>Thu, 05 Apr 2018 18:36:00 +0000</pubDate><guid>https://new.halfrost.com/zh/machine_learning_contents/</guid><description>目录 前言 第一周：Welcome 1.1 What is Machine Learning? 1.2 Linear Regression with One Variable 第二周：Linear Regression with Multiple Variables 2.1 Multivariate Linear Regression 2.2 Computing Parameters Analytically 2.3 Octave/Matlab Tutorial 第三周：Logistic Regression 3.1 Logistic Regression 3.2 Regularization 第四周：Neural Networks: Representation 4.1 Neural Networks Representation 第五周：Neural Networks: Learning 5.1 Neural Networks Learning 5.2 Backpropagation in Practice 第六周：Advice for Applying Machine Learning 6.</description></item><item><title>机器学习应用 —— Photo OCR</title><link>https://new.halfrost.com/zh/application_example_photo_ocr/</link><pubDate>Wed, 04 Apr 2018 18:31:00 +0000</pubDate><guid>https://new.halfrost.com/zh/application_example_photo_ocr/</guid><description>由于 Ghost 博客对 LateX 的识别语法和标准的 LateX 语法有差异，为了更加通用性，所以以下文章中 LateX 公式可能出现乱码，如果出现乱码，不嫌弃的话可以在笔者的 Github 上看这篇无乱码的文章。笔者有空会修复这个乱码问题的。请见谅。
GitHub Repo：Halfrost-Field
Follow: halfrost · GitHub
Source: https://github.com/halfrost/Halfrost-Field/blob/master/contents/Machine_Learning/Application_Photo_OCR.ipynb
一. Photo OCR 1. Problem Description and Pipeline 问题描述 图像文字识别应用所作的事是，从一张给定的图片中识别文字。这比从一份扫描文档中识别文字要复杂的多。
为了完成这样的工作，需要采取如下步骤：
文字侦测（Text detection）——将图片上的文字与其他环境对象分离开来 字符切分（Character segmentation）——将文字分割成一个个单一的字符 字符分类（Character classification）——确定每一个字符是什么 可以用任务流程图来表达这个问题，每一项任务可以由一个单独的小队来负责解决：
2. Sliding Windows 滑动窗口 滑动窗口是一项用来从图像中抽取对象的技术。假使我们需要在一张图片中识别行人，首先要做的是用许多固定尺寸的图片来训练一个能够准确识别行人的模型。然后我们用之前训练识别行人的模型时所采用的图片尺寸在我们要进行行人识别的图片上进行剪裁，然后将剪裁得到的切片交给模型，让模型判断是否为行人，然后在图片上滑动剪裁区域重新进行剪裁，将新剪裁的切片也交给模型进行判断，如此循环直至将图片全部检测完。
一旦完成后，我们按比例放大剪裁的区域，再以新的尺寸对图片进行剪裁，将新剪裁的切片按比例缩小至模型所采纳的尺寸，交给模型进行判断，如此循环。
滑动窗口技术也被用于文字识别，首先训练模型能够区分字符与非字符，然后，运用滑动窗口技术识别字符，一旦完成了字符的识别，我们将识别得出的区域进行一些扩展，然后将重叠的区域进行合并。接着我们以宽高比作为过滤条件，过滤掉高度比宽度更大的区域（认为单词的长度通常比高度要大）。下图中绿色的区域是经过这些步骤后被认为是文字的区域，而红色的区域是被忽略的。
以上便是文字侦测阶段。 下一步是训练一个模型来完成将文字分割成一个个字符的任务，需要的训练集由单个字符的图片和两个相连字符之间的图片来训练模型。
模型训练完后，我们仍然是使用滑动窗口技术来进行字符识别。
以上便是字符切分阶段。 最后一个阶段是字符分类阶段，利用神经网络、支持向量机或者逻辑回归算法训练一个分类器即可。
3. Getting Lots of Data and Artificial Data 人工合成数据 在字符识别阶段，为了更好的完成分类识别任务，我们就需要给系统提供尽可能多的训练图像，如果我们手头上拥有的图像不多，就需要人工合成更多的数据。例如，我们可以收集不同的字体，并为每种字体的每个字符加上随机背景，这样就可以人工扩展大量的字符图像：
另外，也可以通过扭曲字符形状来合成新数据，这也会帮助机器更好地处理发生过形态变化的图像：
但是，为数据加上随机噪声一般不会提升模型训练质量：
4. ceiling analysis 上限分析 在机器学习的应用中，我们通常需要通过几个步骤才能进行最终的预测，我们如何能够知道哪一部分最值得我们花时间和精力去改善呢？这个问题可以通过上限分析来回答。
回到我们的文字识别应用中，我们的流程图如下：
所谓上限分析，就是我们假定某个组件及其前面组件的精度都达到了 100%，即该组件完美地完成了任务，达到了上限，那么此时整个系统的精度能提升多少 。例如，假定整个系统的精度是 72%，我们令文本检测的精度是 100%（比如人工利用 PS 来定位图片中的文本框），此时，整个系统的精度能提升到 89%。即，如果我们付出足够多的精力来优化文本检测，那么理想情况下，能将系统的精度提升 17%：</description></item><item><title>大规模机器学习中如何优化算法？</title><link>https://new.halfrost.com/zh/large_scale_machine_learning/</link><pubDate>Tue, 03 Apr 2018 18:28:00 +0000</pubDate><guid>https://new.halfrost.com/zh/large_scale_machine_learning/</guid><description>由于 Ghost 博客对 LateX 的识别语法和标准的 LateX 语法有差异，为了更加通用性，所以以下文章中 LateX 公式可能出现乱码，如果出现乱码，不嫌弃的话可以在笔者的 Github 上看这篇无乱码的文章。笔者有空会修复这个乱码问题的。请见谅。
GitHub Repo：Halfrost-Field
Follow: halfrost · GitHub
Source: https://github.com/halfrost/Halfrost-Field/blob/master/contents/Machine_Learning/Large_Scale_Machine_Learning.ipynb
一. Gradient Descent with Large Datasets 如果我们有一个低方差的模型，增加数据集的规模可以帮助你获得更好的结果。我们应该怎样应对一个有100万条记录的训练集？
以线性回归模型为例，每一次梯度下降迭代，我们都需要计算训练集的误差的平方和，如果我们的学习算法需要有20次迭代，这便已经是非常大的计算代价。
首先应该做的事是去检查一个这么大规模的训练集是否真的必要，也许我们只用1000个训练集也能获得较好的效果，我们可以绘制学习曲线来帮助判断。
二. Advanced Topics 1. 批量梯度下降法（Batch gradient descent） 拥有了大数据，就意味着，我们的算法模型中得面临一个很大的 m 值。回顾到我们的批量梯度下降法：
重复直到收敛：
$$\theta_j=\theta_j-\alpha \frac{1}{m} \sum_{i=1}^{m}(h_{\theta}(x^{(i)})-y^{(i)})x^{(i)}_j,\;\;\;\;for\;\;j=0,\cdots,n$$
可以看到，每更新一个参数 $\theta_j$ ，我们都不得不遍历一遍样本集，在 m 很大时，该算法就显得比较低效。但是，批量梯度下降法能找到全局最优解：
2. 随机梯度下降法（Stochastic gradient descent） 针对大数据集，又引入了随机梯度下降法，该算法的执行过程为：
重复直到收敛：
$$ \begin{align*} for\;\;\;i&amp;amp;=1,\cdots,m:\
\theta_j&amp;amp;=\theta_j-\alpha(h_\theta(x^{(i)})-y^{(i)})x^{(i)}_j,\;\;\;\;for\;\;j=0,\cdots,n\
\end{align*} $$
相较于批量梯度下降法，随机梯度下降法每次更新 $\theta_j$ 只会用当前遍历的样本。虽然外层循环仍需要遍历所有样本，但是，往往我们能在样本尚未遍历完时就已经收敛，因此，面临大数据集时，随机梯度下降法性能卓越。
上图反映了随机梯度下降法找寻最优解的过程，相较于批量梯度下降法，随机梯度下降法的曲线就显得不是那么平滑，而是很曲折了，其也倾向于找到局部最优解而不是全局最优解。因此，我们通常需要绘制调试曲线来监控随机梯度的工作过程是否正确。例如，假定误差定义为 $cost(\theta,(x^{(i)},y^{(i)}))=\frac{1}{2}(h_\theta(x^{(i)})-y^{(i)})^2$，则每完成 1000 次迭代，即遍历了 1000 个样本，我们求取平均误差并进行绘制，得到误差随迭代次数的变化曲线：</description></item><item><title>推荐系统中的协同过滤和低秩矩阵分解</title><link>https://new.halfrost.com/zh/recommender_systems/</link><pubDate>Mon, 02 Apr 2018 18:25:00 +0000</pubDate><guid>https://new.halfrost.com/zh/recommender_systems/</guid><description>由于 Ghost 博客对 LateX 的识别语法和标准的 LateX 语法有差异，为了更加通用性，所以以下文章中 LateX 公式可能出现乱码，如果出现乱码，不嫌弃的话可以在笔者的 Github 上看这篇无乱码的文章。笔者有空会修复这个乱码问题的。请见谅。
GitHub Repo：Halfrost-Field
Follow: halfrost · GitHub
Source: https://github.com/halfrost/Halfrost-Field/blob/master/contents/Machine_Learning/Recommender_Systems.ipynb
一. Predicting Movie Ratings 以预测第3部电影第1个用户可能评的分数为例子。
首先我们用 $x_1$ 表示爱情浪漫电影类型， $x_2$ 表示动作片类型。上图左表右侧则为每部电影对于这两个分类的相关程度。我们默认 $x_0=1$ 。则第一部电影与两个类型的相关程度可以这样表示： $x^{(3)}=\left[ \begin{array}{ccc}1 \0.99 \0 \end{array} \right]$ 。然后用 $\theta^{(j)}$ 表示第 j 个用户对于该种类电影的评分。这里我们假设已经知道（详情下面再讲） $\theta^{(1)}=\left[ \begin{array}{ccc}0 \5 \0 \end{array} \right]$ ，那么我们用 $(\theta^{(j)})^Tx^{(i)}$ 即可计算出测第3部电影第1个用户可能评的分数。这里计算出是4.95。
1. 目标优化 为了对用户 j 打分状况作出最精确的预测，我们需要：
$$\min_{(\theta^{(j)})}=\frac{1}{2}\sum_{i:r(i,j)=1}^{}{((\theta^{(j)})^T(x^{(i)})-y^{(i,j)})^2}+\frac{\lambda}{2}\sum_{k=1}^{n}{(\theta_k^{(j)})^2}$$
计算出所有的 $\theta$ 为：
$$J(\theta^{(1)},\cdots,\theta^{(n_u)})=\min_{(\theta^{(1)},\cdots,\theta^{(n_u)})}=\frac{1}{2}\sum_{j=1}^{n_u}\sum_{i:r(i,j)=1}^{}{((\theta^{(j)})^T(x^{(i)})-y^{(i,j)})^2}+\frac{\lambda}{2}\sum_{j=1}^{n_u}\sum_{k=1}^{n}{(\theta_k^{(j)})^2}$$
与前面所学线性回归内容的思路一致，为了计算出 $J(\theta^{(1)},\cdots,\theta^{(n_u)})$，使用梯度下降法来更新参数：
更新偏置（插值）：
$$\theta^{(j)}_0=\theta^{(j)}0-\alpha \sum{i:r(i,j)=1}((\theta^{(j)})^Tx^{(i)}-y^{(i,j)})x^{(i)}_0$$
更新权重：
$$\theta^{(j)}_k=\theta^{(j)}k-\alpha \left( \sum{i:r(i,j)=1}((\theta^{(j)})^Tx^{(i)}-y^{(i,j)})x^{(i)}_k+\lambda \theta^{(j)}_k \right),;;; k \neq 0$$</description></item><item><title>机器学习中的异常检测问题</title><link>https://new.halfrost.com/zh/anomaly_detection/</link><pubDate>Sun, 01 Apr 2018 18:12:00 +0000</pubDate><guid>https://new.halfrost.com/zh/anomaly_detection/</guid><description>由于 Ghost 博客对 LateX 的识别语法和标准的 LateX 语法有差异，为了更加通用性，所以以下文章中 LateX 公式可能出现乱码，如果出现乱码，不嫌弃的话可以在笔者的 Github 上看这篇无乱码的文章。笔者有空会修复这个乱码问题的。请见谅。
GitHub Repo：Halfrost-Field
Follow: halfrost · GitHub
Source: https://github.com/halfrost/Halfrost-Field/blob/master/contents/Machine_Learning/Anomaly_Detection.ipynb
一. Density Estimation 密度估计 假如要更为正式定义异常检测问题，首先我们有一组从 $x^{(1)}$ 到 $x^{(m)}$ m个样本，且这些样本均为正常的。我们将这些样本数据建立一个模型 p(x) ， p(x) 表示为 x 的分布概率。
那么假如我们的测试集 $x_{test}$ 概率 p 低于阈值 $\varepsilon$ ，那么则将其标记为异常。
异常检测的核心就在于找到一个概率模型，帮助我们知道一个样本落入正常样本中的概率，从而帮助我们区分正常和异常样本。高斯分布（Gaussian Distribution）模型就是异常检测算法最常使用的概率分布模型。
1. 高斯分布 假如 x 服从高斯分布，那么我们将表示为： $x\sim N(\mu,\sigma^2)$ 。其分布概率为：
$$p(x;\mu,\sigma^2)=\frac{1}{\sqrt{2\pi}\sigma}exp(-\frac{(x-\mu)^2}{2\sigma^2})$$
其中 $\mu$ 为期望值（均值）， $\sigma^2$ 为方差。
其中，期望值 $\mu$ 决定了其轴的位置，标准差 $\sigma$ 决定了分布的幅度宽窄。当 $\mu=0,\sigma=1$ 时的正态分布是标准正态分布。
期望值：$$\mu=\frac{1}{m}\sum_{i=1}^{m}{x^{(i)}}$$
方差： $$\sigma^2=\frac{1}{m}\sum_{i=1}^{m}{(x^{(i)}-\mu)}^2$$
假如我们有一组 m 个无标签训练集，其中每个训练数据又有 n 个特征，那么这个训练集应该是 m 个 n 维向量构成的样本矩阵。</description></item><item><title>PCA 与降维</title><link>https://new.halfrost.com/zh/dimensionality_reduction/</link><pubDate>Sat, 31 Mar 2018 18:09:00 +0000</pubDate><guid>https://new.halfrost.com/zh/dimensionality_reduction/</guid><description>由于 Ghost 博客对 LateX 的识别语法和标准的 LateX 语法有差异，为了更加通用性，所以以下文章中 LateX 公式可能出现乱码，如果出现乱码，不嫌弃的话可以在笔者的 Github 上看这篇无乱码的文章。笔者有空会修复这个乱码问题的。请见谅。
GitHub Repo：Halfrost-Field
Follow: halfrost · GitHub
Source: https://github.com/halfrost/Halfrost-Field/blob/master/contents/Machine_Learning/Dimensionality_Reduction.ipynb
一. Motivation 我们很希望有足够多的特征（知识）来保准学习模型的训练效果，尤其在图像处理这类的任务中，高维特征是在所难免的，但是，高维的特征也有几个如下不好的地方：
学习性能下降，知识越多，吸收知识（输入），并且精通知识（学习）的速度就越慢。 过多的特征难于分辨，你很难第一时间认识某个特征代表的意义。 特征冗余，如下图所示，厘米和英尺就是一对冗余特征，他们本身代表的意义是一样的，并且能够相互转换。 我们使用现在使用了一条绿色直线，将各个样本投影到该直线，那么，原来二维的特征 x=(厘米，英尺) 就被降低为了一维 x=(直线上的相对位置)
而在下面的例子中，我们又将三维特征投影到二位平面，从而将三维特征降到了二维：
特征降维的一般手段就是将高维特征投影到低维空间。
二. Principal Component Analysis 主成分分析 PCA，Principle Component Analysis，即主成分分析法，是特征降维的最常用手段。顾名思义，PCA 能从冗余特征中提取主要成分，在不太损失模型质量的情况下，提升了模型训练速度。
如上图所示，我们将样本到红色向量的距离称作是投影误差（Projection Error）。以二维投影到一维为例，PCA 就是要找寻一条直线，使得各个特征的投影误差足够小，这样才能尽可能的保留原特征具有的信息。
假设我们要将特征从 n 维度降到 k 维：PCA 首先找寻 k 个 n 维向量，然后将特征投影到这些向量构成的 k 维空间，并保证投影误差足够小。下图中中，为了将特征维度从三维降低到二位，PCA 就会先找寻两个三维向量 $\mu^{(1)},\mu^{(2)}$ ，二者构成了一个二维平面，然后将原来的三维特征投影到该二维平面上：
1. 区别 PCA 和 线性回归的区别是：
线性回归找的是垂直于 X 轴距离最小值，PCA 找的是投影垂直距离最小值。</description></item><item><title>无监督学习</title><link>https://new.halfrost.com/zh/unsupervised_learning/</link><pubDate>Fri, 30 Mar 2018 18:05:00 +0000</pubDate><guid>https://new.halfrost.com/zh/unsupervised_learning/</guid><description>由于 Ghost 博客对 LateX 的识别语法和标准的 LateX 语法有差异，为了更加通用性，所以以下文章中 LateX 公式可能出现乱码，如果出现乱码，不嫌弃的话可以在笔者的 Github 上看这篇无乱码的文章。笔者有空会修复这个乱码问题的。请见谅。
GitHub Repo：Halfrost-Field
Follow: halfrost · GitHub
Source: https://github.com/halfrost/Halfrost-Field/blob/master/contents/Machine_Learning/Unsupervised_Learning.ipynb
一. Clustering 1. 定义 从本节开始，将正式进入到无监督学习（Unsupervised Learning）部分。无监督学习，顾名思义，就是不受监督的学习，一种自由的学习方式。该学习方式不需要先验知识进行指导，而是不断地自我认知，自我巩固，最后进行自我归纳，在机器学习中，无监督学习可以被简单理解为不为训练集提供对应的类别标识（label），其与有监督学习的对比如下：
有监督学习（Supervised Learning）下的训练集：
$$\left{ (x^{(1)},y^{(1)}),(x^{(2)},y^{(2)}),\cdots,(x^{(m)},y^{(m)}) \right}$$
无监督学习（Unsupervised Learning）下的训练集：
$$\left{ (x^{(1)}),(x^{(2)}),(x^{(3)}),\cdots,(x^{(m)}) \right}$$
在有监督学习中，我们把对样本进行分类的过程称之为分类（Classification），而在无监督学习中，我们将物体被划分到不同集合的过程称之为聚类（Clustering）。聚这个动词十分精确，他传神地描绘了各个物体自主地想属于自己的集合靠拢的过程。
在聚类中，我们把物体所在的集合称之为簇（cluster）。
2. K-Means 在聚类问题中，我们需要将未加标签的数据通过算法自动分成有紧密关系的子集。那么K均值聚类算法（K-mean）是现在最为广泛使用的聚类方法。
我们执行K均值聚类算法是这样的。首先随机选择两个点，这两个点叫做聚类中心（cluster centroids），也就是图中红色和蓝色的交叉。K均值聚类 一个迭代的方法，它要做两件事，一件是簇分配，另一件是移动聚类中心。
在K均值聚类算法的每次循环里面，第一步要进行的是簇分配。首先要历遍所有的样本，也就是上图中每一个绿色的点，然后根据每一个点是更接近红色的这个中心还是蓝色的这个中心，将每一个数据点分配到两个不同的聚类中心。
例如第一次我们随机定的两个中心点和其簇分配如下图所示：
第二步要做的自然是要移动聚类中心。我们需要将两个中心点移动到刚才我们分成两类的数据各自的均值处。那么所要做的就是找出所有红色的点计算出他们的均值，然后把红色叉叉移动到该均值处，蓝色叉叉亦然。
然后通过不断重复上述两个步骤，通过不断迭代直到其聚类中心不变，那么也就是说K均值聚类已经收敛了，我们就可以从该数据中找到两个最有关联的簇了。其过程大概如下图所示：
K均值聚类算法有两个输入：一个是参数K，也就是你想从数据中聚类出簇的个数。另一个就是只有x没有y的训练集。
以下是 K 均值聚类算法的过程。
第一步是随机初始化K个聚类中心，记做： $\mu_1, \mu_2,\cdots,\mu_k$ 。
第二个大部分就是进行迭代。其中第一个循环是：对于每个训练样本 ，我们用变量 $c^{(i)}$ 表示在 K 个聚类中心里面最接近 $x^{(i)}$ 那个中心的下标。我们可以通过 $min_k||x^{(i)}-\mu_k||$ 进行计算。第二个循环是：移动聚类中心。将 $\mu_k$ 也就是中心点的值 = 刚才我们分好的簇的均值。</description></item><item><title>初探支持向量机</title><link>https://new.halfrost.com/zh/support_vector_machines/</link><pubDate>Thu, 29 Mar 2018 17:47:00 +0000</pubDate><guid>https://new.halfrost.com/zh/support_vector_machines/</guid><description>由于 Ghost 博客对 LateX 的识别语法和标准的 LateX 语法有差异，为了更加通用性，所以以下文章中 LateX 公式可能出现乱码，如果出现乱码，不嫌弃的话可以在笔者的 Github 上看这篇无乱码的文章。笔者有空会修复这个乱码问题的。请见谅。
GitHub Repo：Halfrost-Field
Follow: halfrost · GitHub
Source: https://github.com/halfrost/Halfrost-Field/blob/master/contents/Machine_Learning/Support_Vector_Machines.ipynb
一. 引子 在逻辑回归中，我们的预测函数为：
$$h_\theta(x)=\frac{1}{1+e^{-\theta^Tx}}$$
对于每一个样本 (x,y) 而言（注意是每一个），其代价函数为：
$$ \begin{align*} J(\theta)&amp;amp;=-(ylogh_\theta(x)+(1-y)log(1-h_\theta((x)))\
&amp;amp;=-ylog\frac{1}{1+e^{-\theta^Tx}}-(1-y)log(1-\frac{1}{1+e^{-\theta^Tx}}) \end{align*}\
$$
那么当 y=1 的时候， $J(\theta)=-ylog\frac{1}{1+e^{-\theta^Tx}}$ ，其代价函数的图像入左下图所示。
当 y=0 的时候， $J(\theta)=-(1-y)log(1-\frac{1}{1+e^{-\theta^Tx}})$ ，其代价函数的图像入右下图所示。
对于支持向量机而言，
$y=1$ 的时候：
$$cost_1(\theta^Tx^{(i)})=(-logh_\theta(x^{(i)}))$$
$y=0$ 的时候：
$$cost_0((\theta^Tx^{(i)})=((-log(1-h_\theta(x^{(i)})))$$
当 y=1 时，随着 z 取值变大，预测代价变小，因此，逻辑回归想要在面对正样本 y=1 时，获得足够高的预测精度，就希望 $z= \theta^Tx\gg 0 $ 。而 SVM 则将上图的曲线拉直为下图中的折线，构成了 y=1 时的代价函数曲线 $cost_1(z)$ ：
当 y=1 时，为了预测精度足够高，SVM 希望 $\theta^Tx\geqslant 1$ 。</description></item><item><title>设计一个机器学习系统需要考虑哪些方面？</title><link>https://new.halfrost.com/zh/machine_learning_system_design/</link><pubDate>Wed, 28 Mar 2018 17:38:00 +0000</pubDate><guid>https://new.halfrost.com/zh/machine_learning_system_design/</guid><description>由于 Ghost 博客对 LateX 的识别语法和标准的 LateX 语法有差异，为了更加通用性，所以以下文章中 LateX 公式可能出现乱码，如果出现乱码，不嫌弃的话可以在笔者的 Github 上看这篇无乱码的文章。笔者有空会修复这个乱码问题的。请见谅。
GitHub Repo：Halfrost-Field
Follow: halfrost · GitHub
Source: https://github.com/halfrost/Halfrost-Field/blob/master/contents/Machine_Learning/Machine_Learning_System_Design.ipynb
一. Building a Spam Classifier 垃圾邮件分类就是一个 0/1 分类问题，可以用逻辑回归完成，这里不再重复介绍逻辑回归的过程了，我们考虑如何降低分类错误率：
尽可能的扩大数据样本：Honypot 做了这样一件事，把自己包装成一个对黑客极具吸引力的机器，来诱使黑客进行攻击，就像蜜罐（honey pot）吸引密封那样，从而记录攻击行为和手段。 添加更多特征：例如我们可以增加邮件的发送者邮箱作为特征，可以增加标点符号作为特征（垃圾邮件总会充斥了？，！等吸引眼球的标点）。 预处理样本：正如我们在垃圾邮件看到的，道高一尺，魔高一丈，垃圾邮件的制造者也会升级自己的攻击手段，如在单词拼写上做手脚来防止邮件内容被看出问题，例如把 medicine 拼写为 med1cinie 等。因此，我们就要有手段来识别这些错误拼写，从而优化我们输入到逻辑回归中的样本。 假如我们要用机器学习解决一个问题，那么最好的实践方法就是：
1.建立一个简单的机器学习系统，用简单的算法快速实现它。
2.通过画出学习曲线，以及检验误差，来找出我们的算法是否存在高偏差或者高方差的问题，然后再通过假如更多的训练数据、特征变量等等来完善算法。
3.误差分析。例如在构建垃圾邮件分类器，我们检查哪一类型的邮件或者那些特征值总是导致邮件被错误分类，从而去纠正它。当然，误差的度量值也是很重要的，例如我们可以将错误率表示出来，用来判断算法的优劣。
二. Handling Skewed Data 评估一个模型的好坏，通常使用误差分析可视化，即把预测的准确率（Accuracy）显示出来，其实这样是有缺陷的。这种误差度量又被称为偏斜类（Skewed Classes）问题。
举个例子：假如我们做癌症分析，最后得出该算法只有1%的误差，也就是说准确率达到了99% 。这样看起来99%算是非常高的了，但是我们发现在训练集里面只有0.5%的患者患有癌症，那么这1%的错误率就变得那么准确了。我们再举个极端一点的例子，无论输入是什么，所有预测输出的数据都为0（也就是非癌症），那么我们这里的正确率是99.5%，但是这样的判断标准显然不能体现分类器的性能。
这是因为两者的数据相差非常大，在这里因为癌症的样本非常少，所以导致了预测的结果就会偏向一个极端，我们把这类的情况叫做偏斜类（Skewed Classes）问题。
所以我们需要另一种的评估方法，其中一种评估度量值叫做查准率（Precision）和召回率（Recall）。
建立一个2 x 2的表格，横坐标为真实值，纵坐标为预测值，表格单元1-4分别代表：预测准确的正样本（True positive）、预测错误的正样本（False positive）、预测错误的负样本（False negative）、预测正确的负样本（True negative）。
查准率（Precision）= 预测准确的正样本（True positive）/预测的正样本（predicted positive）,而其中预测的正样本自然就包括了 预测准确的正样本+ 预测错误的正样本。
$$Precision=\frac{True;positive}{Predicated;as;positive }=\frac{True;positive}{True;positive+False;positive}$$</description></item><item><title>机器学习算法评估</title><link>https://new.halfrost.com/zh/advice_for_applying_machine_learning/</link><pubDate>Tue, 27 Mar 2018 17:28:00 +0000</pubDate><guid>https://new.halfrost.com/zh/advice_for_applying_machine_learning/</guid><description>由于 Ghost 博客对 LateX 的识别语法和标准的 LateX 语法有差异，为了更加通用性，所以以下文章中 LateX 公式可能出现乱码，如果出现乱码，不嫌弃的话可以在笔者的 Github 上看这篇无乱码的文章。笔者有空会修复这个乱码问题的。请见谅。
GitHub Repo：Halfrost-Field
Follow: halfrost · GitHub
Source: https://github.com/halfrost/Halfrost-Field/blob/master/contents/Machine_Learning/Advice_for_Applying_Machine_Learning.ipynb
一. Evaluating a Learning Algorithm 想要降低预测误差，即提高预测精度，我们往往会采用这些手段：
采集更多的样本
错误的认为样本越多越好，其实数据多并不是越好。
降低特征维度
降维可能去掉了有用的特征。
采集更多的特征
增加了计算负担，也可能导致过拟合。
进行高次多项式回归
过高的多项式可能造成过拟合。
调试正规化参数 $\lambda$,增大或者减少 $\lambda$
增大或者减少都是凭感觉。
有这么多种解决办法我们怎么知道是哪一种呢？很多人选择这些方法的标准就是凭感觉随便选择一种，然后花很长的时间最后发现是没用的，走上了不归路。所以下面我们介绍一我们需要一种简单有效的办法，我们将其称为机器学习算法诊断（Machine learning diagnostic）。
1. Evaluating a Hypothesis 评价假设函数 首先我们要评估的是我们的假设函数（Hypothesis）。当我们选择特征值或者参数来使训练集误差最小化，但是我们会遇到过拟合的问题，推广到新的训练集就不再使用了。而且当特征量很多的时候，我们就不能将 $J(\theta)$ 可视化看出其是否随着迭代次数而下降了。所以我们采用以下的方法来评估我们的假设函数：
假设有 10 组数据，随机把 70% 做为训练集，剩下的 30% 做为测试集。训练集和测试集尽量保证是随机排列。
接下来：</description></item><item><title>神经网络反向传播实践</title><link>https://new.halfrost.com/zh/backpropagation_in_practice/</link><pubDate>Mon, 26 Mar 2018 08:35:00 +0000</pubDate><guid>https://new.halfrost.com/zh/backpropagation_in_practice/</guid><description>由于 Ghost 博客对 LateX 的识别语法和标准的 LateX 语法有差异，为了更加通用性，所以以下文章中 LateX 公式可能出现乱码，如果出现乱码，不嫌弃的话可以在笔者的 Github 上看这篇无乱码的文章。笔者有空会修复这个乱码问题的。请见谅。
GitHub Repo：Halfrost-Field
Follow: halfrost · GitHub
Source: https://github.com/halfrost/Halfrost-Field/blob/master/contents/Machine_Learning/Backpropagation_in_Practice.ipynb
一. Backpropagation in Practice 为了利用梯度下降的优化算法，需要用到 fminunc 函数。其输入的参数是 $\theta$ ，函数的返回值是代价函数 jVal 和导数值 gradient。然后将返回值传递给高级优化算法 fminunc，然后输出为输入值 @costFunction，以及 $\theta$ 值的初始值。
其中参数 $\Theta_1,\Theta_2,\Theta_3,\cdots$ 和 $D^{(1)},D^{(2)},D^{(3)},\cdots$ 都为矩阵，那么为了能调用 fminunc 函数，我们要将其变成向量，
假如我们 $\Theta_1,\Theta_2,\Theta_3$ 参数和 $D^{(1)},D^{(2)},D^{(3)}$ 参数，Theta1 是 $10 * 11$，Theta2 是 $10 * 11$，Theta3 是 $1 * 11$。
% 打包成一个向量 thetaVector = [ Theta1(:); Theta2(:); Theta3(:); ] deltaVector = [ D1(:); D2(:); D3(:) ] % 解包还原 Theta1 = reshape(thetaVector(1:110),10,11) Theta2 = reshape(thetaVector(111:220),10,11) Theta3 = reshape(thetaVector(221:231),1,11) 所以套路是：</description></item><item><title>神经网络反向传播算法推导</title><link>https://new.halfrost.com/zh/neural_networks_learning/</link><pubDate>Sun, 25 Mar 2018 08:33:00 +0000</pubDate><guid>https://new.halfrost.com/zh/neural_networks_learning/</guid><description>由于 Ghost 博客对 LateX 的识别语法和标准的 LateX 语法有差异，为了更加通用性，所以以下文章中 LateX 公式可能出现乱码，如果出现乱码，不嫌弃的话可以在笔者的 Github 上看这篇无乱码的文章。笔者有空会修复这个乱码问题的。请见谅。
GitHub Repo：Halfrost-Field
Follow: halfrost · GitHub
Source: https://github.com/halfrost/Halfrost-Field/blob/master/contents/Machine_Learning/Neural_Networks_Learning.ipynb
一. Cost Function and Backpropagation 1. Cost Function 假设训练集中有 m 个训练样本，$\begin{Bmatrix} (x^{(1)},y^{(1)}),(x^{(2)},y^{(2)}), \cdots ,(x^{(m)},y^{(m)}) \end{Bmatrix}$，L 表示神经网络的总层数 Layer，用 $S_{l}$ 表示第 L 层的单元数(神经元的数量)，但是不包括第 L 层的偏差单元(常数项)。令 K 为输出层的单元数目，即 最后一层的单元数。
符号约定：
$z_i^{(j)}$ = 第 $j$ 层的第 $i$ 个节点（神经元）的“计算值” $a_i^{(j)}$ = 第 $j$ 层的第 $i$ 个节点（神经元）的“激活值” $\Theta^{(l)}{i,j}$ = 映射第 $l$ 层到第 $l+1$ 层的权值矩阵的第 $i$ 行第 $j$ 列的分量 $L$ = 神经网络总层数（包括输入层、隐层和输出层） $s_l$ = 第 $l$ 层节点（神经元）个数，不包括偏移量节点。 $K$ = 输出节点个数 $h{\theta}(x)_k$ = 第 $k$ 个预测输出结果 $x^{(i)}$ = 第 $i$ 个样本特征向量 $x^{(i)}_k$ = 第 $i$ 个样本的第 $k$ 个特征值 $y^{(i)}$ = 第 $i$ 个样本实际结果向量</description></item><item><title>初探神经网络</title><link>https://new.halfrost.com/zh/neural_networks_representation/</link><pubDate>Sat, 24 Mar 2018 08:27:00 +0000</pubDate><guid>https://new.halfrost.com/zh/neural_networks_representation/</guid><description>由于 Ghost 博客对 LateX 的识别语法和标准的 LateX 语法有差异，为了更加通用性，所以以下文章中 LateX 公式可能出现乱码，如果出现乱码，不嫌弃的话可以在笔者的 Github 上看这篇无乱码的文章。笔者有空会修复这个乱码问题的。请见谅。
GitHub Repo：Halfrost-Field
Follow: halfrost · GitHub
Source: https://github.com/halfrost/Halfrost-Field/blob/master/contents/Machine_Learning/Neural_Networks_Representation.ipynb
一. Motivations 假如我们用之前的逻辑回归解决以下分类问题：
我们需要构造一个有很多项的非线性的逻辑回归函数。当只有两个特征量的时候，这还算比较简单的，但是假如我们有100个特征量呢？我们只考虑二阶项的话，其二阶项的个数大约是 $\frac{n^2}{2}$ 。假如我们要包含所有的二阶项的话这样看起来不是一个好办法，因为项数实在太多运算量也很多，而且最后结果往往容易造成过拟合。当然我们只是考虑了二阶项，考虑二阶项以上的就更多了。
当初始特征个数 n 增大时，这些高阶多项式项数将以几何级数上升，特征空间也会随之急剧膨胀 。所以当特征个数 n比较大的时候，用这个方法建立分类器并不是一个好的做法。
而对于大多数的机器学习问题， n 一般是比较大的。
对一个拥有很多特征的复杂数据集进行线性回归是代价很高的。比如我们对 50 * 50 像素的黑白图分类，我们就拥有了 2500 个特征。如果我们还要包含所有二次特征，复杂度为 $O(n^{2}/2)$，也就是说一共要有 $2500^{2}/2=3125000$ 个特征。这样计算的代价是高昂的。
人工神经网络是对具有很多特征的复杂问题进行机器学习的一种方法。
二. Neural Networks 人工神经网络是对生物神经网络的一种简化的模拟。那么，我们先从生物中的神经元入手，进而了解神经网络的工作方式。
用一个简单的模型来模拟神经元的工作，我们将神经元模拟成一个逻辑单元：
$x_{1},x_{2},x_{3}$ 可以将其看成输入神经树突，黄色的圆圈则可以看成中心处理器细胞核， $h_\theta(x)$ 则可看成输出神经轴突。因为这里是逻辑单元，所以我们的输出函数为： $h_\theta(x)=\frac{1}{1+e^{-\theta^Tx}}$ 。一般我们把这称为一个有 s 型函数（逻辑函数）作为激励的人工神经元。
那么神经网络其实就是这些神经元组合在一起的集合，如下图：
左边第一层 Layer1 被称为输入层。在输入层我们输入我们的特征项 $x_{1},x_{2},x_{3}$ 。
右边最后一层被称为输出层。输出函数为： $h_\Theta(x)$ 。
中间这层被称为隐藏层。</description></item><item><title>什么是正则化？</title><link>https://new.halfrost.com/zh/regularization/</link><pubDate>Fri, 23 Mar 2018 08:16:00 +0000</pubDate><guid>https://new.halfrost.com/zh/regularization/</guid><description>由于 Ghost 博客对 LateX 的识别语法和标准的 LateX 语法有差异，为了更加通用性，所以以下文章中 LateX 公式可能出现乱码，如果出现乱码，不嫌弃的话可以在笔者的 Github 上看这篇无乱码的文章。笔者有空会修复这个乱码问题的。请见谅。
GitHub Repo：Halfrost-Field
Follow: halfrost · GitHub
Source: https://github.com/halfrost/Halfrost-Field/blob/master/contents/Machine_Learning/Regularization.ipynb
一. Solving the Problem of Overfitting 考虑从 $x \in \mathbb{R}$ 预测 y 的问题。下面最左边的图显示了将 $y =\theta_{0}+\theta_{1}x$ 拟合到数据集的结果。我们看到这些数据并不是直线的，所以这个数据并不是很好。
相反，如果我们添加了一个额外的特征 x2，并且拟合 $y =\theta_{0}+\theta_{1}x+\theta_{2}x^{2}$，那么我们获得的数据稍微更适合,如上图。
但是并不是添加的多项式越多越好。但是，添加太多特征也是一个危险：最右边的数字是拟合五阶多项式 $y =\theta_{0}+\theta_{1}x+\theta_{2}x^{2}+\theta_{3}x^{3}+\theta_{4}x^{4}+\theta_{5}x^{5} $ 的结果。我们看到即使拟合曲线完美地传递了数据，我们也不会认为这是一个很好的预测，上图最右边的图就是过度拟合的例子。
上图最右边的图也称有高方差。如果我们拟合一个高阶多项式，有过度的特征，并且这个假设函数能拟合几乎所有的数据，这就面临可能的函数太过于庞大，变量太多的问题。我们没有足够的数据去约束它，来获得一个好的假设函数，这就是过度拟合。
欠拟合或高偏倚是当我们的假设函数h的形式很难与数据的趋势作图时。它通常是由一个功能太简单或功能太少造成的。另一方面，过度拟合或高度方差是由适合现有数据的假设函数引起的，但不能很好地预测新数据。它通常是由一个复杂的函数造成的，它会产生大量与数据无关的不必要的曲线和角度。
这个术语适用于线性和逻辑回归。解决过度配合问题有两个主要选项：
1. 减少特征的数量： 手动选择要保留的特征，哪些变量更为重要，哪些变量应该保留，哪些应该舍弃。 使用模型选择算法（稍后在课程中学习），算法会自动选择哪些特征变量保留，哪些舍弃。 缺点是舍弃了一些特征以后，也就舍弃了一些问题的关键信息。
2. 正则化 保留所有的特征，但减少参数 $\theta_{j}$ 的大小或者减少量级。 当有很多个特征的时候，并且每个特征都会对最终预测值产生影响，正则化可以保证运作良好。 正则化目的是尽量去简化这个假设模型。因为这些参数都接近0的时候，越简单的模型也被证明越不容易出现过拟合的问题。
减少一些数量级的特征，加一些“惩罚”项(为了使代价函数最小，乘以 1000 就是惩罚)。
代价函数：
$$ \rm{CostFunction} = \rm{F}({\theta}) = \frac{1}{2m} \left [ \sum_{i = 1}^{m} (h_{\theta}(x^{(i)})-y^{(i)})^2 + \lambda \sum_{i = 1}^{m} \theta_{j}^{2} \right ]$$</description></item><item><title>逻辑回归</title><link>https://new.halfrost.com/zh/logistic_regression/</link><pubDate>Thu, 22 Mar 2018 08:00:00 +0000</pubDate><guid>https://new.halfrost.com/zh/logistic_regression/</guid><description>由于 Ghost 博客对 LateX 的识别语法和标准的 LateX 语法有差异，为了更加通用性，所以以下文章中 LateX 公式可能出现乱码，如果出现乱码，不嫌弃的话可以在笔者的 Github 上看这篇无乱码的文章。笔者有空会修复这个乱码问题的。请见谅。
GitHub Repo：Halfrost-Field
Follow: halfrost · GitHub
Source: https://github.com/halfrost/Halfrost-Field/blob/master/contents/Machine_Learning/Logistic_Regression.ipynb
一. Classification and Representation 要尝试分类，一种方法是使用线性回归，并将所有大于0.5的预测值映射为1，将小于0.5的所有预测值映射为0.但是，此方法效果不佳，因为分类实际上不是线性函数。 分类问题就像回归问题一样，除了我们现在想要预测的值只有少数离散值。
线性回归用来解决分类问题，通常不是一个好主意。
我们解决分类问题，忽略y是离散值，并使用我们的旧线性回归算法来尝试预测给定的x。但是，构建这种方法性能很差的示例很容易。直观地说，当知道$y\in \begin{Bmatrix} 0,1 \end{Bmatrix}$时，$h_{\theta}(x)$ 取大于1或小于0的值也是没有意义的。为了解决这个问题，让我们改变我们的假设 $h_{\theta}(x)$ 的形式以满足 $0\leqslant h_{\theta}(x)\leqslant 1$。这是通过将 $\theta^{T}x$ 插入 Logistic 函数来完成的：
$$g(x) = \frac{1}{1+e^{-x}}$$
上式称为 Sigmoid Function 或者 Logistic Function
令 $h_{\theta}(x) = g(\theta^{T}x)$,$z = \theta^{T}x$,则:
$$g(x) = \frac{1}{1+e^{-\theta^{T}x}}$$
这里显示的函数$g(x)$将任何实数映射到（0,1）区间，使得它可用于将任意值函数转换为更适合分类的函数。
决策边界不是训练集的属性，而是假设本身及其参数的属性。
二. Logistic Regression Model 1. Cost Function 之前定义的代价函数：</description></item><item><title>Octave Matlab 教程</title><link>https://new.halfrost.com/zh/octave_matlab_tutorial/</link><pubDate>Thu, 22 Mar 2018 07:56:00 +0000</pubDate><guid>https://new.halfrost.com/zh/octave_matlab_tutorial/</guid><description>由于 Ghost 博客对 LateX 的识别语法和标准的 LateX 语法有差异，为了更加通用性，所以以下文章中 LateX 公式可能出现乱码，如果出现乱码，不嫌弃的话可以在笔者的 Github 上看这篇无乱码的文章。笔者有空会修复这个乱码问题的。请见谅。
GitHub Repo：Halfrost-Field
Follow: halfrost · GitHub
Source: https://github.com/halfrost/Halfrost-Field/blob/master/contents/Machine_Learning/Octave_Matlab_Tutorial.ipynb
一. Basic Operations 基本操作 在 matlab 中，%是注释符号。基础的数学运算有以下这些：
1 + 2 %加 5 - 6 %减 7 * 4 %乘 9 / 3 %除 6 ^ 4 %幂指数 log(20) %对数运算 exp(30) %指数运算 abs(-2) %绝对值运算 1. 逻辑运算： 1 == 2 %判断是否相等 ans = 0 %false -------------------------------------------------------------------------------------------- 1 ~= 2 % ~= 是 ！= 的意思，判断是否不相等 ans = 1 %True -------------------------------------------------------------------------------------------- 1 &amp;amp;&amp;amp; 0 %逻辑 AND （和运算） ans = 0 -------------------------------------------------------------------------------------------- 1 || 0 %逻辑 OR（或运算） ans = 1 -------------------------------------------------------------------------------------------- xor(1,0) %异或运算 ans = 1 -------------------------------------------------------------------------------------------- 2.</description></item><item><title>计算参数分析 —— 正规方程法</title><link>https://new.halfrost.com/zh/computing_parameters_analytically/</link><pubDate>Wed, 21 Mar 2018 07:50:00 +0000</pubDate><guid>https://new.halfrost.com/zh/computing_parameters_analytically/</guid><description>由于 Ghost 博客对 LateX 的识别语法和标准的 LateX 语法有差异，为了更加通用性，所以以下文章中 LateX 公式可能出现乱码，如果出现乱码，不嫌弃的话可以在笔者的 Github 上看这篇无乱码的文章。笔者有空会修复这个乱码问题的。请见谅。
GitHub Repo：Halfrost-Field
Follow: halfrost · GitHub
Source: https://github.com/halfrost/Halfrost-Field/blob/master/contents/Machine_Learning/Computing_Parameters_Analytically.ipynb
一. Normal Equation 1. 正规方程 正规方程法相对梯度下降法，它可以一步找到最小值。而且它也不需要进行特征值的缩放。
样本集是 $ m * n $ 的矩阵，每行样本表示为 $ \vec{x^{(i)}} $ ,第 i 行第 n 列分别表示为 $ x^{(i)}{0} , x^{(i)}{1} , x^{(i)}{2} , x^{(i)}{3} \cdots x^{(i)}_{n} $, m 行向量分别表示为 $ \vec{x^{(1)}} , \vec{x^{(2)}} , \vec{x^{(3)}} , \cdots \vec{x^{(m)}} $
令
$$ \vec{x^{(i)}} = \begin{bmatrix} x^{(i)}{0}\ x^{(i)}{1}\ \vdots \ x^{(i)}_{n}\ \end{bmatrix} $$</description></item><item><title>多元线性回归</title><link>https://new.halfrost.com/zh/multivariate_linear_regression/</link><pubDate>Tue, 20 Mar 2018 07:47:00 +0000</pubDate><guid>https://new.halfrost.com/zh/multivariate_linear_regression/</guid><description>由于 Ghost 博客对 LateX 的识别语法和标准的 LateX 语法有差异，为了更加通用性，所以以下文章中 LateX 公式可能出现乱码，如果出现乱码，不嫌弃的话可以在笔者的 Github 上看这篇无乱码的文章。笔者有空会修复这个乱码问题的。请见谅。
GitHub Repo：Halfrost-Field
Follow: halfrost · GitHub
Source: https://github.com/halfrost/Halfrost-Field/blob/master/contents/Machine_Learning/Multivariate_Linear_Regression.ipynb
一. Multiple Features 具有多个变量的线性回归也被称为“多元线性回归”。
$x_{j}^{(i)}$: 训练集第 i 个向量中的第 j 个元素(第 i 行第 j 列)
$x^{(i)}$: 训练集第 i 个向量(第 i 行)
$ m $: 总共 m 行
$ n $: 总共 n 列
适应这些多特征的假设函数的多变量形式如下：
$$ h_{\theta}(x) = \theta_{0} + \theta_{1}x_{1} + \theta_{2}x_{2} + \theta_{3}x_{3} + \cdots + \theta_{n}x_{n} $$
使用矩阵乘法的定义，我们的多变量假设函数可以简洁地表示为：</description></item><item><title>如何理解梯度下降？</title><link>https://new.halfrost.com/zh/gradient_descent/</link><pubDate>Sun, 18 Mar 2018 21:54:00 +0000</pubDate><guid>https://new.halfrost.com/zh/gradient_descent/</guid><description>由于 Ghost 博客对 LateX 的识别语法和标准的 LateX 语法有差异，为了更加通用性，所以以下文章中 LateX 公式可能出现乱码，如果出现乱码，不嫌弃的话可以在笔者的 Github 上看这篇无乱码的文章。笔者有空会修复这个乱码问题的。请见谅。
GitHub Repo：Halfrost-Field
Follow: halfrost · GitHub
Source: https://github.com/halfrost/Halfrost-Field/blob/master/contents/Machine_Learning/Gradient_descent.ipynb
一. Model Representation 在给定训练集的情况下，学习函数h：X→Y，使得h（x）是y的相应值的“好”预测器。由于历史原因，这个函数h被称为假设。
通过输入住房面积 x，通过学习好的函数，输出房子的估价。
二. Cost Function 代价函数是线性回归中的一个应用，在线性回归中，要解决的一个问题就是最小化问题。
假设在一元线性回归中，在一个训练集中，我们需要找到一条直线能和该训练集中的点最接近。假设直线方程为
$$h_{\theta}(x) = \theta_{0} + \theta_{1}x$$
如何选择 $\theta_{0}$、$\theta_{1}$，使得 $h_{\theta}(x)$ 更接近于训练集 (x,y) ？
上述问题可以转换为求 $$ \rm{CostFunction} = \rm{F}({\theta_{0}},{\theta_{1}}) = \frac{1}{2m}\sum_{i = 1}^{m} (h_{\theta}(x^{(i)})-y^{(i)})^2 $$ 求最小值$$\min_{{\theta_{0}} {\theta_{1}}} \rm{F}({\theta_{0},{\theta_{1}})} $$
三. Gradient Descent 梯度下降 梯度下降的主要思想：
初始化 ${\theta_{0}}$ 和 ${\theta_{1}}$ , ${\theta_{0}}$ = 0 , ${\theta_{1}}$ = 0 不断的改变 ${\theta_{0}}$ 和 ${\theta_{1}}$ 值，不断减少 $F({\theta_{0}},{\theta_{1}})$ 直至达到最小值（或者局部最小）。 想象成下山，如何下山的速度最快？这里涉及到了下山的速度，即步长。</description></item><item><title>什么是机器学习？</title><link>https://new.halfrost.com/zh/what_is_machine_learning/</link><pubDate>Sun, 18 Mar 2018 06:30:00 +0000</pubDate><guid>https://new.halfrost.com/zh/what_is_machine_learning/</guid><description>一. What is Machine Learning? 在机器学习的历史上，一共出现了两种定义。
1956 年，开发了西洋跳棋 AI 程序的 Arthur Samuel 在标志着人工智能学科诞生的达特茅斯会议上定义了 “机器学习” 这个词，定义为，“在没有明确设置的情况下，使计算机具有学习能力的研究领域”。
1997 年，Tom Mitchell 提供了一个更现代的定义：“如果用 P 来测量程序在任务 T 中性能。若一个程序通过利用经验 E 在 T 任务中获得了性能改善，则我们就说关于任务 T 和 性能测量 P ，该程序对经验 E 进行了学习。”
例如：玩跳棋。
E = 玩很多盘跳棋游戏的经验
T = 玩跳棋的任务。
P = 程序将赢得下一场比赛的概率。
二. Classify 一般来说，任何机器学习问题都可以分配到两大类中的一个：
有监督学习 supervised learning 和无监督学习 unsupervised learning。
简单的说，监督学习就是我们教计算机去做某件事情，无监督学习是我们让计算机自己学习。
1. supervised learning 在监督式学习中，首先有一个数据集，并且已知正确的输出是什么，且输入和输出存在关联。 监督学习问题分为“回归 Regression”和“分类 Classification”问题。
在回归问题中，我们试图预测连续输出中的结果，这意味着我们试图将输入变量映射到某个连续函数。例如给定一个人的照片，根据照片预测年龄，这就是一个回归的问题。
在分类问题中，我们试图预测离散输出中的结果。换句话说，我们试图将输入变量映射到离散类别中。例如给予患有肿瘤的患者，我们必须预测肿瘤是恶性的还是良性的。
2. unsupervised learning 无监督学习使我们能够很少或根本不知道我们的结果应该是什么样子。我们可以从数据中得出结构，我们不一定知道变量的影响。 我们可以通过基于数据中变量之间的关系对数据进行聚类来推导出这种结构。 在无监督学习的情况下，没有基于预测结果的反馈。无监督学习可以分为“聚类”和“非聚类”。</description></item><item><title>Threes-AI 玩小三传奇 (上)</title><link>https://new.halfrost.com/zh/threes-ai/</link><pubDate>Wed, 14 Feb 2018 15:35:00 +0000</pubDate><guid>https://new.halfrost.com/zh/threes-ai/</guid><description>AI for the Threes! game.
灵感来源 1 个月前和另外二位小伙伴一起参加了一个 AI 的比赛。虽然比赛结果不理想，至少我享受到了编程过程中的乐趣。从这次比赛中让我认识到 Go 除了写服务端，写游戏模拟器，写 AI 都是拿手好戏。最近微信跳一跳的辅助，冲顶大会的辅助基本也都是 Go 写的。于是我更坐不住了，也写一个来纪念我们那次比赛。
由于本人也是客户端出身，所以这个 AI 必须也能在手机上刷分。所以要找一个手机游戏，三个人可以玩的，或者名字带“三”字的，由此：
Threes preson join one AI competition &amp;mdash;&amp;gt; Threes-AI
“炫耀”分数 目前这个 Go 版本的 AI 在 3 个地方跑了分，都分别跑了 200 盘。拿到高分的比例差不多就 20% 左右。所以也希望能在项目第二阶段——机器学习阶段，能把跑高分的比率提高到 100%
1. play threes game 官方网站 这个网站就是官方游戏的 web 版了。
这个高分视频在这里，腾讯视频链接
2. threes Android 客户端 这里之所以没有跑 iOS 客户端的游戏截图，是因为 iOS 客户端需要越狱才能运行，笔者手头上的机器都在 iOS 11.2+，等以后越狱了可以再重新来跑跑分。
3. threes game 自建网站 为了能自己通过机器学习训练模型，也为了能公开展示这个 AI 的实力，于是按照官方的游戏规则，原汁原味的复刻了一个 web 版。</description></item><item><title>Google S2 是如何解决空间覆盖最优解问题的?</title><link>https://new.halfrost.com/zh/go_s2_regioncoverer/</link><pubDate>Wed, 10 Jan 2018 09:19:00 +0000</pubDate><guid>https://new.halfrost.com/zh/go_s2_regioncoverer/</guid><description>前言 这篇不出意外就是 Google S2 整个系列的最终篇了。这篇里面会把 regionCoverer 算法都讲解清楚。至于 Google S2 库里面还有很多其他的小算法，代码同样也很值得阅读和学习，这里也就不一一展开了，有兴趣的读者可以把整个库都读一遍。
在写这篇文章的同时，发现了库的作者的一些新的 commit ，比如 2017年12月4号的 commit f9610db2b871b54b17d36d4da6a4d6a2aab6018d，这次提交的改动更改了 README，别看只改了文档，其实里面内容很多。
-For an analogous library in C++, see -https://code.google.com/archive/p/s2-geometry-library/, and in Java, see -https://github.com/google/s2-geometry-library-java ------------------------------------------------------------------------------------ +For an analogous library in C++, see https://github.com/google/s2geometry, in +Java, see https://github.com/google/s2-geometry-library-java, and Python, see +https://github.com/google/s2geometry/tree/master/src/python 可以看到他们把原来存在 Google 官方私有代码仓库里面的代码放到了 github。之前都只能在代码归档里面查看 C++ 代码，现在直接可以在 github 上查看了。方便了很多。
+More details about S2 in general are available on the S2 Geometry Website +[s2geometry.</description></item><item><title>【2017年终总结】程序员如何在技术浪潮的更迭中保持较高的成长速度 ？</title><link>https://new.halfrost.com/zh/halfrost_2017/</link><pubDate>Fri, 29 Dec 2017 23:11:00 +0000</pubDate><guid>https://new.halfrost.com/zh/halfrost_2017/</guid><description>题记 作为技术人，到年底都会进行一次自我反思或者总结，回过头来看看这一年自己成长了多少。笔者也不例外，同样打算从 2017 年开始记录自己的年终总结。虽然这种总结的文章不算纯技术文章，但是为了避免记流水账，所以想尽脑汁想以一种新颖的方式展现在读者面前。于是打算用一个大家比较关心的问题来贯穿全文。不出意外，以后每年的形式都会如此。一年一个宏观的问题。文章中的经历保证都是笔者百分之百亲生经历的，有成功的案例也有失败的案例，当然读者自身的情况也会与笔者不同，各位读者可以根据自身的情况来取长补短。文章中的一些观点仅代表笔者个人的一些看法，如有不妥，欢迎提出来一起讨论和批判。
笔者在网上的中文笔名是“一缕殇流化隐半边冰霜”，常常被人简称为“冰霜”、“霜菜”、“霜”。这一年一篇的年度文章既然这么特别，那么就给这个系列文章取一个特别的名字吧。在中国的文化中，四个字的成语读起来能更加有内涵，成语里面也必须带“霜”字和光阴意思，通过大量的搜寻以后，就定下了这个系列的标题的名字 —— 星霜荏苒。
【解释】星霜：星辰运转一年一次循环，每年秋季始降霜，因以批岁月。指岁月渐渐流逝。
【出处】唐·温庭筠《寄崔先生》：“星霜荏苒无音信，烟水微茫变姓名。”
好了，本系列的文章该说明的地方都说明完了，以后每年就不做此说明了。正文正式开始。
技术更迭是有加速度的 从 2010 年开始，被定义为移动互联网的元年，移动开发也是从这一年开始逐渐开始火爆的。笔者也是从毕业之后加入这个浪潮的。据说移动开发火爆之时，理发师通过几个月培训以后也可以拿到月薪1，2W的薪水，可见那个时候对移动人才的饥渴程度。但是到了 2014 年底开始，移动开发的入职要求回归理性，要求逐渐提高，到现在基本大公司社招也不再招高级以下的移动开发了。面试当然也比之前几年难度提高了不少。BAT 的面试可能会考察前沿技术，热修复和跨平台，底层技术，LLVM + Clang ，基础技术，WebKit 和 JSCore 。身边一部分 iOS 开发也逐渐开发转写 JavaScript 了。国内 iOS 开发者也可能会觉得大前端时代的到来，对自己技术的冲击。（当然国外的 iOS 开发者对这些并不感冒，国外的玩法还是原生开发。）继续回到国内的行情，当大前端的一些东西逐渐吞噬 iOS 开发者的开发领域的时候，也许还没有等大家熟悉或者精通前端各种框架的时候，这时 AI 又出现在大家视野中了。机器学习，深度学习一大堆的概念如潮水般涌来。
2012 年底到 2013 年初，有大批创业公司如雨后春笋般的出生。到了 2014 年底，也有大批的公司没有活过那年的冬天。到了今年 2017 年，依旧有大批的创业公司出生又倒下，如各个单车公司。在资本的市场里就是如此的残酷。
周围的一些同事也有出现焦虑的，说实话，我也焦虑过。风口技术恨不得一年一变。年初的区块链和三大前端框架可能还没有玩转，年底立即就被 AI 又碾压一波。一位前端大神这样和我解释到，“技术就需要时刻的跟着，前端如果几个月不跟新技术，看到新技术可能会陌生。如果守着老技术几年不变，呵呵，可能再了解新技术的时候，前端框架已经换了新天地了。”也许这个回答带有一些夸张成分，但是从侧面也能看出前端这几年的发展速度之快。
大家也可以回想一下近几年技术的更迭，也许也能感受到，技术更迭是有加速度的。
焦虑的起源？ 我还是以 iOS 开发者来举例。常常会在 iOS 开发者群里出现的 3 个图。
一个是 iOS 开发没人要了和 iOS 开发又有人要了。这 2 幅图常常被作为一个梗出现在各个讨论群中。原因为何？因为 iOS 开发的一部分需求被前端开发者承担了。国内很多小公司招聘要求上也直接写的要求会 JS 、RN、Weex 等技术。直接导致不会 JS 的 iOS 开发就没人要了。每每苹果对跨平台或者热修复进行“封杀”或者任何举措的时候，原生开发都会刷一波 iOS 又有人要了的表情。前端也许同样会有焦虑，焦虑也许来自于对三大框架的掌握程度，如果只精通一个框架，找工作遇到了精通三大框架的人也会心虚。</description></item><item><title>四叉树上如何求希尔伯特曲线的邻居 ？</title><link>https://new.halfrost.com/zh/go_s2_hilbert_neighbor/</link><pubDate>Wed, 15 Nov 2017 03:12:00 +0000</pubDate><guid>https://new.halfrost.com/zh/go_s2_hilbert_neighbor/</guid><description>关于邻居的定义，相邻即为邻居，那么邻居分为2种，边相邻和点相邻。边相邻的有4个方向，上下左右。点相邻的也有4个方向，即4个顶点相邻的。
如上图，绿色的区域是一颗四叉树表示的范围，四叉树上面有一个点，图中黄色区域标明的点。现在想求四叉树上黄色的点的希尔伯特曲线邻居。图中黑色的线就是一颗穿过四叉树的希尔伯特曲线。希尔伯特曲线的起点0在左上角的方格中，终点63在右上角的方格中。
红色的四个格子是黄色格子边相邻邻居，蓝色的四个格子是黄色格子的顶点相邻的邻居，所以黄色格子的邻居为8个格子，分别表示的点是8，9，54，11，53，30，31，32 。可以看出来这些邻居在表示的点上面并不是相邻的。
那么怎么求四叉树上任意一点的希尔伯特曲线邻居呢？
一. 边邻居 边邻居最直接的想法就是 先拿到中心点的坐标 (i，j) ，然后通过坐标系的关系，拿到与它边相邻的 Cell 的坐标 (i + 1，j) ， (i - 1，j) ， (i，j - 1) ， (i，j + 1) 。
实际做法也是如此。不过这里涉及到需要转换的地方。这里需要把希尔伯特曲线上的点转换成坐标以后才能按照上面的思路来计算边邻居。
关于 CellID 的生成与数据结构，见笔者这篇《Google S2 中的 CellID 是如何生成的 ？》
按照上述的思路，实现出来的代码如下：
func (ci CellID) EdgeNeighbors() [4]CellID { level := ci.Level() size := sizeIJ(level) f, i, j, _ := ci.faceIJOrientation() return [4]CellID{ cellIDFromFaceIJWrap(f, i, j-size).Parent(level), cellIDFromFaceIJWrap(f, i+size, j).Parent(level), cellIDFromFaceIJWrap(f, i, j+size).Parent(level), cellIDFromFaceIJWrap(f, i-size, j).</description></item><item><title>Google S2 中的 CellID 是如何生成的 ？</title><link>https://new.halfrost.com/zh/go_s2_cellid/</link><pubDate>Thu, 02 Nov 2017 08:37:00 +0000</pubDate><guid>https://new.halfrost.com/zh/go_s2_cellid/</guid><description>笔者在《高效的多维空间点索引算法 — Geohash 和 Google S2》文章中详细的分析了 Google S2 的算法实现思想。文章发出来以后，一部分读者对它的实现产生了好奇。本文算是对上篇文章的补充，将从代码实现的角度来看看 Google S2 的算法具体实现。建议先读完上篇文章里面的算法思想，再看本篇的代码实现会更好理解一些。
一. 什么是 Cell ？ Google S2 中定义了一个将单位球体分解成单元格层次结构的框架。每个 Cell 的单元格是由四个测地线限定的四边形。通过将立方体的六个面投影到单位球上来获得层级的顶层，通过递归地将每个单元细分为四个子层来获得较低层。例如，下面的图片显示了六个 face 中 Cell 的两个，其中一个已经细分了几次：
注意 Cell 边缘似乎是弯曲的，这是因为它们是球形测地线，即球体上的直线（类似于飞机飞行的路线）
层次结构中的每个单元格都有一个 level 级别，定义为单元格细分的次数（以面单元格开始）。细胞水平范围从 0 到 30。在 level - 30 的最小细胞被称为叶细胞，总共有6 * 4^30^个，每个在地球表面 1cm 左右。 （每个级别的单元格大小的细节可以在S2 Cell ID 数据结构找到）
S2 Level 对于空间索引和将区域逼近为单元集合非常有用。Cell 可用于表示点和区域：点通常表示为叶子节点，而区域表示为任何 Level 的 Cell 的集合。例如，下面是夏威夷近似的22个单元的集合：
二. S(lat,lng) -&amp;gt; f(x,y,z) 纬度 Latitude 的取值范围在 [-90°,90°] 之间。 经度 Longitude 的取值范围在 [-180°,180°] 之间。
第一步转换，将球面坐标转换成三维直角坐标
func makeCell() { latlng := s2.</description></item><item><title>神奇的德布鲁因序列</title><link>https://new.halfrost.com/zh/go_s2_de_bruijn/</link><pubDate>Fri, 27 Oct 2017 00:12:00 +0000</pubDate><guid>https://new.halfrost.com/zh/go_s2_de_bruijn/</guid><description>数学中存在这样一个序列，它充满魔力，在实际工程中也有一部分的应用。今天就打算分享一下这个序列，它在 Google S2 中是如何使用的以及它在图论中，其他领域中的应用。这个序列就是德布鲁因序列 De Bruijn。
一. 从一个魔术开始说起 有这样一个扑克牌魔术。魔术师手上拿着一叠牌，给5个人(这里的人数只能少于等于32，原因稍后会解释)分别检查扑克牌，查看扑克牌的花色和点数是否都是不同的，即没有相同的牌。
检查完扑克牌，没有重复的牌以后，就可以给这5个人洗牌了。让这5个人任意的抽一叠牌从上面放到下面，即切牌。5个人轮流切完牌，牌的顺序已经全部变化了。
接着开始抽牌。魔术师让最后一个切牌的人抽走这叠牌最上面的一张，依次给每个人抽走最上面的一张。这时候抽走了5张牌。魔术师会说，“我已经看透了你们的心思，你们手上的牌我都知道了”。然后魔术师会让拿黑色牌的人站起来(这一步很关键！)。然后魔术师会依次说出所有人手上的牌。最后每个人翻出自己的牌，全部命中。全场欢呼。
二. 魔术原理揭秘 在整个魔术中，有三个地方比较关键。第一个是参与的人数只能少于等于32 。一副完整的扑克牌中，总共有54张牌，但是除去2张鬼牌(因为他们花色只有2种)，总共就52张牌。
在上述魔术中，所有的牌都用二进制进行编码，要想任意说出任意连续的5张牌，那么必须这副牌具有全排列的特性。即枚举所有种组合，并且每个组合都唯一代表了一组排列。
如果窗口大小为5，5张连续的扑克牌。二进制编码 2^5^ = 32 ，所以需要32张牌。如果窗口大小为6，6张连续的扑克牌，二进制编码 2^6^ = 64，需要64张扑克牌。总共牌只有52张，所以不可能到64张。所以32个人是上限了 。
第二个关键的地方是，只有让拿黑色的牌的人或者拿红色的牌的人站出来，魔术师才能知道这5个人拿到的连续5张扑克牌究竟是什么。其实魔术师说“我已经知道你们所有人拿到的是什么牌”的时候，他并不知道每个人拿到的是什么牌。
扑克牌除了点数以外，还有4种花色。现在需要32张牌，就是1-8号牌，每号牌都有4种花色。花色用2位二进制编码，1-8用3位二进制编码。于是5位二进制正好可以表示一张扑克牌所有信息。
如上图，00110 表示的就是梅花6 。11000 表示的是红桃8（因为没有 0 号牌，所以000就表示8）
第一步将扑克牌编码完成以后，第二步就需要找到一个序列，它必须满足以下的条件：由 2^n-1^个1和2^n-1^个0构成的序列或者圆排列，是否能存在在任意 n 个位置上0，1序列两两都不同。满足这个条件的序列也称为 n 阶完备二进圆排列。
这个魔术中我们需要找的是 5 阶完备二进圆排列。答案是存在这样一个满足条件的序列。这个序列也就是文章的主角，德布鲁因序列。
上述序列就是一个窗口大小为5的德布鲁因序列。任意连续的5个二进制相互之间都是两两不同的。所以给观众任意洗牌，不管怎么洗牌，只要最终挑出来是连续的5张，这5张的组合都在最终的结果之中。
将窗口大小为5的德布鲁因序列每5个二进制位都转换成扑克牌的编码，如下：
所以32张牌的初始顺序如下：
梅花8，梅花A，梅花2，梅花4，黑桃A，方片2，梅花5，黑桃3，方片6，黑桃4，红桃A，方片3，梅花7，黑桃7，红桃7，红桃6，红桃4，红桃8，方片A，梅花3，梅花6，黑桃5，红桃3，方片7，黑桃6，红桃5，红桃2，方片5，黑桃2，方片4，黑桃8，方片8。
上面这32张牌的初始顺序魔术师是要记在心里的。
将所有的排列组合列举出来，如上图。当魔术师让黑色或者红色的牌的人出列的时候，就能确定到具体是哪一种组合了。于是也就可以直接说出每个人手上拿的是什么牌了。
这个魔术中选取的德布鲁因序列也非常特殊，是可以通过一部分的递推得到。
这个特殊的序列，任意取出其中一个窗口，即5个连续的二进制，5个二进制的第一位和第三位，或者倒数第三位和倒数第五位相加，加法遵循二进制规则，即可得到这个窗口紧接着的下一位。
如上图的例子，假设当前窗口里面的五位是 00001，左数第一位加上第三位，或者右数第三位加上第五位，得到的是0，那么这个窗口紧接着的后一位就是0 ，即 000010 。再举一个例子，当前窗口里面是 11000 ，左数第一位加上第三位为1，所以紧接着的下一位是1，即 110001 。
最后一个关键的地方就在切牌的手法上。由于德布鲁因序列是一个循环的序列，为了维护这个序列，切牌只能把上面的牌切到下面去，不能乱切。只有上面的牌切到下面，因为循环的原因，这样才不能影响德布鲁因序列。
魔术能成功实施，必要条件就是要先记住32张牌的初始位置。然后切牌的时候暗示观众牌是洗过的。最后根据观众拿了黑色花色的牌(梅花和黑桃)举手，快速定位到5个连续的牌位于32张牌的哪个窗口，然后说出这5张牌的花色和点数。
三. 德布鲁因序列的定义和性质 1. 定义 德布鲁因序列(De Bruijn sequence)，记为B(k, n)，是 k 元素构成的循环序列。所有长度为 n 的 k 元素构成序列都在它的子序列（以环状形式）中，出现并且仅出现一次。</description></item><item><title>Google S2 中的四叉树求 LCA 最近公共祖先</title><link>https://new.halfrost.com/zh/go_s2_lowest_common_ancestor/</link><pubDate>Fri, 20 Oct 2017 03:18:00 +0000</pubDate><guid>https://new.halfrost.com/zh/go_s2_lowest_common_ancestor/</guid><description>一. 寻找父亲节点和孩子节点 首先需要回顾一下希尔伯特曲线的生成方式，具体代码见笔者上篇文章的分析，在这个分析中，有4个方向比较重要，接下来的分析需要，所以把这4个方向的图搬过来。
在举例之前还需要说明一点，有些网站提供的二进制转换，并没有标明有符号还是无符号的转换，这样就会导致使用者的一些误解。笔者开始并没有发现这个问题，导致掉入了这个坑，好一会才转过弯来。笔者在网上查询了很多在线转换计算器的工具，都发现了这个问题。比如常见的在线进制转换http://tool.oschina.net/hexconvert，随便找两个64位的二进制数，有符号的和无符号的分别转换成十进制，或者反过来转换，你会惊喜的发现，两次结果居然相同！例如你输入 3932700003016900608 和 3932700003016900600，你会发现转换成二进制以后结果都是 11011010010011110000011101000100000000000000000000000000000000。但是很明显这两个数不同。
假如 3932700003016900608 是无符号的，3932700003016900600 是有符号的，正确的结果应该如下：
// 3932700003016900608 11011010010011110000011101000100000000000000000000000000000000 // 3932700003016900600 11011010010011110000011101000011111111111111111111111111111000 差距明显很大。这种例子其实还有很多，随便再举出几组：无符号的 3932700011606835200 和有符号的 3932700011606835000；无符号的 3932700020196769792 和有符号的 3932700020196770000；无符号的 3932700028786704384 和有符号的 3932700028786704400……可以举的例子很多，这里就不再举了。
利用网上的这个工具，十进制转二进制是无符号的转换，二进制转十进制就会变成有符号的转换了。而 Google S2 默认是无符号的 CellID，所以用有符号的 CellID 会出现错误。所以转换中需要注意一下。笔者之前没有发现这一点的时候出现了一些问题，后来突然发现了这一点，茅塞顿开。
好了，进入正题。接下来直接看一个例子，笔者用例子来说明每个 Cell 之间的关系，以及如何查找父亲节点的。
假设有上图这4个连在一起的 Cell。先根据经纬度把 CellID 计算出来。
对应的4个 CellID 分别是：
由于前两位都是0，所以其实可以省略，但是为了读者看的更清晰，笔者还是补全了64位，前面2个0还是补上 // 3932700003016900608 右上 0011011010010011110000011101000100000000000000000000000000000000 // 3932700011606835200 左上 0011011010010011110000011101001100000000000000000000000000000000 // 3932700020196769792 左下 0011011010010011110000011101010100000000000000000000000000000000 // 3932700028786704384 右下 0011011010010011110000011101011100000000000000000000000000000000 在前篇文章里面我们也分析了 Cell 64位的结构，这里是4个 Level 14的 Cell，所以末尾有 64 - 3 - 1 - 14 * 2 = 32 个 0 。从末尾往前的第33位是一个1，第34位，第35位是我们重点需要关注的。可以看到分别是00，01，10，11 。正好是连续的4个二进制。</description></item><item><title>如何设计并实现一个线程安全的 Map ？(下篇)</title><link>https://new.halfrost.com/zh/go_map_chapter_two/</link><pubDate>Wed, 04 Oct 2017 20:58:00 +0000</pubDate><guid>https://new.halfrost.com/zh/go_map_chapter_two/</guid><description>在上篇中，我们已经讨论过如何去实现一个 Map 了，并且也讨论了诸多优化点。在下篇中，我们将继续讨论如何实现一个线程安全的 Map。说到线程安全，需要从概念开始说起。
线程安全就是如果你的代码块所在的进程中有多个线程在同时运行，而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的，而且其他的变量的值也和预期的是一样的，就是线程安全的。
如果代码块中包含了对共享数据的更新操作，那么这个代码块就可能是非线程安全的。但是如果代码块中类似操作都处于临界区之中，那么这个代码块就是线程安全的。
通常有以下两类避免竞争条件的方法来实现线程安全：
第一类 —— 避免共享状态 1.可重入 Re-entrancy 通常在线程安全的问题中，最常见的代码块就是函数。让函数具有线程安全的最有效的方式就是使其可重入。如果某个进程中所有线程都可以并发的对函数进行调用，并且无论他们调用该函数的实际执行情况怎么样，该函数都可以产生预期的结果，那么就可以说这个函数是可重入的。
如果一个函数把共享数据作为它的返回结果或者包含在它返回的结果中，那么该函数就肯定不是一个可重入的函数。任何内含了操作共享数据的代码的函数都是不可重入的函数。
为了实现线程安全的函数，把所有代码都置放于临界区中是可行的。但是互斥量的使用总会耗费一定的系统资源和时间，使用互斥量的过程总会存在各种博弈和权衡。所以请合理使用互斥量保护好那些涉及共享数据操作的代码。
注意：可重入只是线程安全的充分不必要条件，并不是充要条件。这个反例在下面会讲到。
2.线程本地存储
如果变量已经被本地化，所以每个线程都有自己的私有副本。这些变量通过子程序和其他代码边界保留它们的值，并且是线程安全的，因为这些变量都是每个线程本地存储的，即使访问它们的代码可能被另一个线程同时执行，依旧是线程安全的。
3.不可变量
对象一旦初始化以后就不能改变。这意味着只有只读数据被共享，这也实现了固有的线程安全性。可变（不是常量）操作可以通过为它们创建新对象，而不是修改现有对象的方式去实现。 Java，C＃和 Python 中的字符串的实现就使用了这种方法。
第二类 —— 线程同步 第一类方法都比较简单，通过代码改造就可以实现。但是如果遇到一定要进行线程中共享数据的情况，第一类方法就解决不了了。这时候就出现了第二类解决方案，利用线程同步的方法来解决线程安全问题。
今天就从线程同步开始说起。
一. 线程同步理论 在多线程的程序中，多以共享数据作为线程之间传递数据的手段。由于一个进程所拥有的相当一部分虚拟内存地址都可以被该进程中所有线程共享，所以这些共享数据大多是以内存空间作为载体的。如果两个线程同时读取同一块共享内存但获取到的数据却不同，那么程序很容易出现一些 bug。
为了保证共享数据一致性，最简单并且最彻底的方法就是使该数据成为一个不变量。当然这种绝对的方式在大多数情况下都是不可行的。比如函数中会用到一个计数器，记录函数被调用了几次，这个计数器肯定就不能被设为常量。那这种必须是变量的情况下，还要保证共享数据的一致性，这就引出了临界区的概念。
临界区的出现就是为了使该区域只能被串行的访问或者执行。临界区可以是某个资源，也可以是某段代码。保证临界区最有效的方式就是利用线程同步机制。
先介绍2种共享数据同步的方法。
1. 互斥量 在同一时刻，只允许一个线程处于临界区之内的约束称为互斥，每个线程在进入临界区之前，都必须先锁定某个对象，只有成功锁定对象的线程才能允许进入临界区，否则就会阻塞。这个对象称为互斥对象或者互斥量。
一般我们日常说的互斥锁就能达到这个目的。
互斥量可以有多个，它们所保护的临界区也可以有多个。先从简单的说起，一个互斥量和一个临界区。
(一) 一个互斥量和一个临界区 上图就是一个互斥量和一个临界区的例子。当线程1先进入临界区的时候，当前临界区处于未上锁的状态，于是它便先将临界区上锁。线程1获取到临界区里面的值。
这个时候线程2准备进入临界区，由于线程1把临界区上锁了，所以线程2进入临界区失败，线程2由就绪状态转成睡眠状态。线程1继续对临界区的共享数据进行写入操作。
当线程1完成所有的操作以后，线程1调用解锁操作。当临界区被解锁以后，会尝试唤醒正在睡眠的线程2。线程2被唤醒以后，由睡眠状态再次转换成就绪状态。线程2准备进入临界区，当临界区此处处于未上锁的状态，线程2便将临界区上锁。
经过 read、write 一系列操作以后，最终在离开临界区的时候会解锁。
线程在离开临界区的时候，一定要记得把对应的互斥量解锁。这样其他因临界区被上锁而导致睡眠的线程还有机会被唤醒。所以对同一个互斥变量的锁定和解锁必须成对的出现。既不可以对一个互斥变量进行重复的锁定，也不能对一个互斥变量进行多次的解锁。
如果对一个互斥变量锁定多次可能会导致临界区最终永远阻塞。可能有人会问了，对一个未锁定的互斥变成解锁多次会出现什么问题呢？
在 Go 1.8 之前，虽然对互斥变量解锁多次不会引起任何 goroutine 的阻塞，但是它可能引起一个运行时的恐慌。Go 1.8 之前的版本，是可以尝试恢复这个恐慌的，但是恢复以后，可能会导致一系列的问题，比如重复解锁操作的 goroutine 会永久的阻塞。所以 Go 1.8 版本以后此类运行时的恐慌就变成了不可恢复的了。所以对互斥变量反复解锁就会导致运行时操作，最终程序异常退出。
(二) 多个互斥量和一个临界区 在这种情况下，极容易产生线程死锁的情况。所以尽量不要让不同的互斥量所保护的临界区重叠。
上图这个例子中，一个临界区中存在2个互斥量：互斥量 A 和互斥量 B。</description></item><item><title>如何理解 n 维空间和 n 维时空</title><link>https://new.halfrost.com/zh/n-dimensional_space_and_n-dimensional_space-time/</link><pubDate>Sat, 30 Sep 2017 10:01:10 +0000</pubDate><guid>https://new.halfrost.com/zh/n-dimensional_space_and_n-dimensional_space-time/</guid><description>前言 一定有朋友好奇为何笔者会突然发这样一篇看似和技术完全不相干的文章出来。其实这块内容也是笔者在研究时空搜索的时候引申出来的内容。看了一些资料，加深了对 n 维空间和 n 维时空的理解，就总结了一下。如果是对这方面完全没有接触的朋友一开始看一定会觉得陌生，如果是数学专业或者专业就是这方向的朋友，文章如有错误，欢迎提出来一起讨论。
空间和时空 首先空间和时空是常常会被混淆的两个概念。其实他们两者不同。
爱因斯坦的广义相对论里面提到过四维空间，讲的是三维空间加一维时间。这个并不是数学里面的多维空间的概念。实际上，时间维是独立于空间维的。一维空间也可以有时间，二维空间也有时间。多维空间都有时间。但是广义相对论里面提到的四维空间实际上是三维空间加上一维时间组成的四维时空。
黎曼几何之后的高维几何发展了很多年，在超弦理论里宇宙的空间是九维空间加一维时间。而在 M 理论里，宇宙是十维空间加一维时间的十一维时空。
如何描述高维空间的划分 在二维的空间中，两条垂直相交的直线，可以构成 X 轴和 Y 轴。在三维的空间中，三条相互垂直相交的指向，构成了 X 轴，Y轴和 Z 轴。第三条直线穿过二维空间中交点(即原点)，并且垂直于二维空间。同理，在四维空间中，同样会有一条直线，穿过这三维空间的三条直线的交叉点(三维坐标轴的原点)，并垂直于前面三条直线。四维空间中垂直于三维空间的这条线，是无法在三维空间中表示出来的，也无法画出来。这条线位于坐标原点内部的四维空间中。
那么四维空间如何形象的和三维空间产生联系呢？毕竟三维空间是我们人类最最熟悉的空间结构。我们知道三维空间有 X 轴，Y轴，Z轴，那么它们三条轴线能把整个空间分为6个面，上下，左右，前后。那四维空间还能怎么划分空间呢？它比三维空间还多出了里外，两个方向。里面的上面，和外面的上面是不同的空间。虽然在三维空间中都是上面。
同理，我们将这些理论继续推广到高维空间中，那么一定存在一条线能垂直于 n-1 条线，并且 n-1 条线也是相互垂直相交的。
以上就是通过空间划分的角度来描述多维空间。
高维空间事物的形态 在高维空间中，事物都是非常抽象的，可能无法用图形画出来，但是我们可以通过我们能理解的低维空间去理解高维，这就需要研究高维空间事物在低维空间的展现形态了。
在二维空间中，正三角形有三个顶点。并且假设边长都等于1。如果在空间中存在第四个点，能使得这个点到三个顶点的距离都等于1。那么这个点必定不存在在二维空间中，且一定存在于三维空间中(此处数学证明省略，太难了，感兴趣的同学可以证明一下)。如果在三维空间中把这四个点都连接起来，那么就可以构成一个三维的正四面体。
同理，如果有第五个点能和这个三维的正四面体距离都是1，那么这个点也一定存在于四维空间中，与三维的正四面体一起构成四维的超四面体。
超四面体已经超出了我们生活的维度了，所以我们无法在三维空间中画中它的形状。但是我们可以通过投影的方式来在三维空间中去观察它。
先来回顾一点三维的正四面体是怎么产生的。由于是等边三角形，所以等边三角形的垂心到三个顶点的距离一定是相等的。那么我们就把这个内心取出来，拉到三维空间中，直到距离其他三个顶点的距离为1。这样就生成了三维的正四面体。由垂心分割的内部三个钝角三角形跟随着垂心的，拉出去就会变成正四面体外面的3个面。
同理，在三维的正四面体的中，取出它的垂心。垂心与四个顶点的距离都相等。这个垂心就将正四面体在内部分割成了4个扁四面体。那么将垂心拉到四维空间中做第五个顶点的话，就会变成超四面体。内部分割的4个扁四面体也会进化成超四面体的四个外表面。
四维的超四面体是5个顶点，10条棱边，10个三角面，5个四面体构成的超体。用三维空间无法描述它。
正方体是我们常见的三维物体。那四维空间里面的立方体变成什么样子了呢？
上图就是四维空间里面的立方体，叫超立方体。
上图反映出四维方体每条边等长，也可以看出立方体如何互相连接的。构造一个超立方体的最简单的步骤就是把2个立方体的8个顶点都分别和另外一个超立方体的顶点连接起来。
上图揭示的是超立方体本质上是从结合2个立方体，连接对应顶点得来的。
上图按着每一顶点由最底一顶点出发沿着棱走的长度排列。如果我们是要将超正方体用作在并行计算中连接不同处理器网络拓扑基础，则这些图像会非常有用。在超正方体中任意两个顶点之间之间至多有4中不同的路程，并且这里有许多路径是等同的。超正方体还是一个二分图，就像正方形和立方体一样。
下面的两个图是透视投影图
上图是正八胞体绕着一个从左前到右后，从上到下切过图形的平面进行单旋转时的透视投影。
上图是正八胞体绕着两个在四维空间中互相正交的平面进行双旋转时的透视投影。
另外四维空间与以上的空间，属于高维模型。高维模型，也分数学与物理两个概念。
在数学上，多维有很多模型。理论上，维数可以很高。模型很多。但是满足交换不变性质的很少，所以，有人认为四维空间是物理上限。但是，也有人认为会有更高维数物理。去思考，有益智力，因为只受到数学条件约束。
在物理上，多维有很多模型。理论上，维数不可以很高。为了解释，宇宙整体的有限无边的性质，必须引入多维，一般是四维时空（一对相对组成性质），也有一些其它有限可数的维数，可能在物理上成立的模型不多。去思考难度很大，因为要受到物理现象的约束。
透视 和 穿墙术 真的不存在么？ 蚂蚁眼中的世界近乎是二维的，在它的眼里只有长度和宽度，而没有高度。任何三维的物体对于它来说都是一个“面”，它就会去爬。再或者是二维空间里，生活在清明上河图里的人，他们眼中的世界就只有杂乱无章的点线面，画中的人是无法对整幅画中的世界有一个完整的认识的。但是生活在三维空间的我们却可以一眼看到整个画中的世界。同理，处于三维空间里面的我们，看三维空间的物体，也是无法一眼看完的。比如眼前的高楼大厦，要想看完它的四周加上楼顶和楼底，我们无法一眼看完，需要围绕一圈才行。但是这些在四维空间里面，四维空间里面的生物看高楼其实一眼就能看到它是什么样子的。
于是可以得到一个也许不太正确的结论，低维空间不过是高维空间的表皮，因为低维空间是由高维空间中某个维度坍塌导致退化成了“皮”。
回想一下之前讲到的二维的等边三角形，三维的正四面体，四维的超四面体，低维不就是高维的皮么？处于高维看低维，一览无遗。
再比如素描中所说的“透视”，通过一些成像原理，能看到物体被遮挡的部位。当然不是真实的看见。如果是真实的看见，那么这个“透视”就是穿越了维度。
再说说西游记里面的孙悟空画圈保护唐僧，在二维空间里面，这个圈完全可以保护好唐僧，但是到了三维空间中，只需要轻轻的跳出这个圈子，就能摆脱悟空的束缚。在三维空间中想保护一个人，就需要用一个封闭的空间来把他关起来。但是这个人如果是四维空间的人，那么他也能很轻易的跳出这个四维空间。这就是三维空间里面的人无法理解“穿墙术”，但是四维空间里面的人却可以很轻松的做到。
变形金刚真的不存在么？ 在我们生活的三维空间中，能不断的变化自己形态的生物不多。三维世界里面能像变形金刚那样变形的真的不多，尤其是能从内到外的变形。那么在高维空间的世界里，存在变形金刚这种事物么？
答案是同一维也许不多，但是跨空间维的有很多。
比如三维空间里面的一个立方体或者多面体，二维的事物是如何理解它们呢？
举一个双曲线的例子：
两个倒立的圆锥，顶对着顶放置。用一个平面去切割它们，三维物体在这个平面上留下的曲线，我们叫做圆锥曲线。当曲面切的方向不同也就可以形成不同的圆锥曲线，有圆形，有抛物线，有双曲线，有椭圆。
在二维的世界里，只能认识这几种不同的圆锥曲线。但是在三维的世界里，我们就能理解到这是两个圆锥。
上图也很明显的展示了高维空间里面的物体在平面上的切面不同，展示的形状也不同。
那我们扩展到四维空间，如果一个四维空间的物体，被三维空间不断的切面，在三维空间上留下的三维体，不就是会不断变化的么？
所以我们不能理解变形金刚是因为我们处于低维空间中，在高维物体被低维空间切割的时候，就会发生变形金刚的现象。
时间真的不能逆转么？ 小时候经常会考虑这样一件时间，时光真的不能逆转么？破镜真的不能重圆么？想说明白这间事情，就必须先谈谈我们现在所在的四维时空。</description></item><item><title>如何设计并实现一个线程安全的 Map ？(上篇)</title><link>https://new.halfrost.com/zh/go_map_chapter_one/</link><pubDate>Sun, 10 Sep 2017 01:50:00 +0000</pubDate><guid>https://new.halfrost.com/zh/go_map_chapter_one/</guid><description>Map 是一种很常见的数据结构，用于存储一些无序的键值对。在主流的编程语言中，默认就自带它的实现。C、C++ 中的 STL 就实现了 Map，JavaScript 中也有 Map，Java 中有 HashMap，Swift 和 Python 中有 Dictionary，Go 中有 Map，Objective-C 中有 NSDictionary、NSMutableDictionary。
上面这些 Map 都是线程安全的么？答案是否定的，并非全是线程安全的。那如何能实现一个线程安全的 Map 呢？想回答这个问题，需要先从如何实现一个 Map 说起。
一. 选用什么数据结构实现 Map ？ Map 是一个非常常用的数据结构，一个无序的 key/value 对的集合，其中 Map 所有的 key 都是不同的，然后通过给定的 key 可以在常数时间 O(1) 复杂度内查找、更新或删除对应的 value。
要想实现常数级的查找，应该用什么来实现呢？读者应该很快会想到哈希表。确实，Map 底层一般都是使用数组来实现，会借用哈希算法辅助。对于给定的 key，一般先进行 hash 操作，然后相对哈希表的长度取模，将 key 映射到指定的地方。
哈希算法有很多种，选哪一种更加高效呢？
1. 哈希函数 MD5 和 SHA1 可以说是目前应用最广泛的 Hash 算法，而它们都是以 MD4 为基础设计的。
MD4(RFC 1320) 是 MIT 的Ronald L. Rivest 在 1990 年设计的，MD 是 Message Digest（消息摘要） 的缩写。它适用在32位字长的处理器上用高速软件实现——它是基于 32位操作数的位操作来实现的。 MD5(RFC 1321) 是 Rivest 于1991年对 MD4 的改进版本。它对输入仍以512位分组，其输出是4个32位字的级联，与 MD4 相同。MD5 比 MD4 来得复杂，并且速度较之要慢一点，但更安全，在抗分析和抗差分方面表现更好。</description></item><item><title>深入解析 Go 中 Slice 底层实现</title><link>https://new.halfrost.com/zh/go_slice/</link><pubDate>Fri, 25 Aug 2017 07:23:00 +0000</pubDate><guid>https://new.halfrost.com/zh/go_slice/</guid><description>切片是 Go 中的一种基本的数据结构，使用这种结构可以用来管理数据集合。切片的设计想法是由动态数组概念而来，为了开发者可以更加方便的使一个数据结构可以自动增加和减少。但是切片本身并不是动态数据或者数组指针。切片常见的操作有 reslice、append、copy。与此同时，切片还具有可索引，可迭代的优秀特性。
一. 切片和数组 关于切片和数组怎么选择？接下来好好讨论讨论这个问题。
在 Go 中，与 C 数组变量隐式作为指针使用不同，Go 数组是值类型，赋值和函数传参操作都会复制整个数组数据。
func main() { arrayA := [2]int{100, 200} var arrayB [2]int arrayB = arrayA fmt.Printf(&amp;#34;arrayA : %p , %v\n&amp;#34;, &amp;amp;arrayA, arrayA) fmt.Printf(&amp;#34;arrayB : %p , %v\n&amp;#34;, &amp;amp;arrayB, arrayB) testArray(arrayA) } func testArray(x [2]int) { fmt.Printf(&amp;#34;func Array : %p , %v\n&amp;#34;, &amp;amp;x, x) } 打印结果：
arrayA : 0xc4200bebf0 , [100 200] arrayB : 0xc4200bec00 , [100 200] func Array : 0xc4200bec30 , [100 200] 可以看到，三个内存地址都不同，这也就验证了 Go 中数组赋值和函数传参都是值复制的。那这会导致什么问题呢？</description></item><item><title>高效的多维空间点索引算法 — Geohash 和 Google S2</title><link>https://new.halfrost.com/zh/go_spatial_search/</link><pubDate>Fri, 11 Aug 2017 21:52:32 +0000</pubDate><guid>https://new.halfrost.com/zh/go_spatial_search/</guid><description>引子 每天我们晚上加班回家，可能都会用到滴滴或者共享单车。打开 app 会看到如下的界面：
app 界面上会显示出自己附近一个范围内可用的出租车或者共享单车。假设地图上会显示以自己为圆心，5公里为半径，这个范围内的车。如何实现呢？最直观的想法就是去数据库里面查表，计算并查询车距离用户小于等于5公里的，筛选出来，把数据返回给客户端。
这种做法比较笨，一般也不会这么做。为什么呢？因为这种做法需要对整个表里面的每一项都计算一次相对距离。太耗时了。既然数据量太大，我们就需要分而治之。那么就会想到把地图分块。这样即使每一块里面的每条数据都计算一次相对距离，也比之前全表都计算一次要快很多。
我们也都知道，现在用的比较多的数据库 MySQL、PostgreSQL 都原生支持 B+ 树。这种数据结构能高效的查询。地图分块的过程其实就是一种添加索引的过程，如果能想到一个办法，把地图上的点添加一个合适的索引，并且能够排序，那么就可以利用类似二分查找的方法进行快速查询。
问题就来了，地图上的点是二维的，有经度和纬度，这如何索引呢？如果只针对其中的一个维度，经度或者纬度进行搜索，那搜出来一遍以后还要进行二次搜索。那要是更高维度呢？三维。可能有人会说可以设置维度的优先级，比如拼接一个联合键，那在三维空间中，x，y，z 谁的优先级高呢？设置优先级好像并不是很合理。
本篇文章就来介绍2种比较通用的空间点索引算法。
一. GeoHash 算法 1. Geohash 算法简介 Geohash 是一种地理编码，由 Gustavo Niemeyer 发明的。它是一种分级的数据结构，把空间划分为网格。Geohash 属于空间填充曲线中的 Z 阶曲线（Z-order curve）的实际应用。
何为 Z 阶曲线？
上图就是 Z 阶曲线。这个曲线比较简单，生成它也比较容易，只需要把每个 Z 首尾相连即可。
Z 阶曲线同样可以扩展到三维空间。只要 Z 形状足够小并且足够密，也能填满整个三维空间。
说到这里可能读者依旧一头雾水，不知道 Geohash 和 Z 曲线究竟有啥关系？其实 Geohash算法 的理论基础就是基于 Z 曲线的生成原理。继续说回 Geohash。
Geohash 能够提供任意精度的分段级别。一般分级从 1-12 级。
还记得引语里面提到的问题么？这里我们就可以用 Geohash 来解决这个问题。
我们可以利用 Geohash 的字符串长短来决定要划分区域的大小。这个对应关系可以参考上面表格里面 cell 的宽和高。一旦选定 cell 的宽和高，那么 Geohash 字符串的长度就确定下来了。这样我们就把地图分成了一个个的矩形区域了。
地图上虽然把区域划分好了，但是还有一个问题没有解决，那就是如何快速的查找一个点附近邻近的点和区域呢？</description></item><item><title>初探 Go 的编译命令执行过程</title><link>https://new.halfrost.com/zh/go_command/</link><pubDate>Fri, 04 Aug 2017 11:10:00 +0000</pubDate><guid>https://new.halfrost.com/zh/go_command/</guid><description>引言 Go 语言这两年在语言排行榜上的上升势头非常猛，Go 语言虽然是静态编译型语言，但是它却拥有脚本化的语法，支持多种编程范式(函数式和面向对象)。Go 语言最最吸引人的地方可能是其原生支持并发编程(语言层面原生支持和通过第三方库支持是有很大区别的)。Go 语言的对网络通信、并发和并行编程的支持度极高，从而可以更好地利用大量的分布式和多核的计算机。开发者可以通过 goroutine 这种轻量级线程的概念来实现这个目标，然后通过 channel 来实现各个 goroutine 之间的通信。他们实现了分段栈增长和 goroutine 在线程基础上多路复用技术的自动化。
2017年7月 TIOBE 语言排行榜 Go 首次进入前十。今天就让我们来探究探究 Go 的编译命令执行过程。
一. 理解 Go 的环境变量 1. GOROOT 该环境变量的值为 Go 语言的当前安装目录。
2. GOPATH 该环境变量的值为 Go 语言的工作区的集合（意味着可以有很多个）。工作区类似于工作目录。每个不同的目录之间用：分隔。
工作区是放置 Go 源码文件的目录。一般情况下，Go 源码文件都需要存放到工作区中。
工作区一般会包含3个子文件夹，自己手动新建以下三个目录：src 目录，pkg 目录，bin 目录。
/home/halfrost/gorepo ├── bin ├── pkg └── src 这里需要额外说的一点：关于 IDE 新建 Go 项目。IDE 在新建完 Go 的项目以后，会自动的执行 go get 命令去把相应的基础包拉过来， 在这个过程中会新建 bin、pkg、src 三个目录。不用 IDE 的同学，需要自己手动创建这三个目录。
上图是 Atom 的 go-plus 插件在一个新的项目打开的时候，自动 go get 的一些基础包。</description></item><item><title>Go 初学者成长之路</title><link>https://new.halfrost.com/zh/new_gopher/</link><pubDate>Sat, 22 Jul 2017 05:50:00 +0000</pubDate><guid>https://new.halfrost.com/zh/new_gopher/</guid><description>开源书籍 编译器 1. Vim党 Vim党当然是不需要 IDE 的，直接 Vim + Vim-go（或者 Emacs）
2. 文本编辑器 + 插件 目前最常用最火的文本编辑器有 VSCode、Sublime、Atom
他们都可以安装相应的插件，就可以支持 Go 的编码了。我暂时用的是 Atom + go-plus，界面还比较美，如下图：
3. IDE 目前用的比较多的 IDE 有：IntelliJ idea、Goland、LiteIDE。
学习网站 视频 这个也看个人吧，有些人不喜欢看文档，或者有时候文档看累了看会视频。下面这些视频本人看过开头的，觉得讲的还可以，不过后面的我没有继续看下去了，因为觉得看视频学习有点慢，我还是选择看刷书刷题啦~
社区 最后，多多练习，多多实践 Go，只要功夫深，铁杵磨成针！
（由于 Ghost 不支持 MarkDown 里面的表格语法，所以文章中的表格都是图片格式，链接都不能点，如果想看有链接的版本，请点下面这个 Github 库里面的文章，那篇是带可点链接的版本。）
GitHub Repo：Halfrost-Field
Follow: halfrost · GitHub
Source: https://halfrost.com/new_gopher/</description></item><item><title>JSConf China 2017 Day Two — End And Beginning</title><link>https://new.halfrost.com/zh/jsconf_china_2017_final/</link><pubDate>Sat, 15 Jul 2017 09:25:00 +0000</pubDate><guid>https://new.halfrost.com/zh/jsconf_china_2017_final/</guid><description>第二天的分享更加偏向 Web 后端。
第一场：Node.js Microservices on Autopilot 开场简单介绍了一下什么是微服务。
微服务有什么帮助 假想步骤：
把 corn 服务分解成许多较小服务 每个微服务都可以独立部署 新的微服务都可以负载均衡 当微服务架构与他们所替代的服务相同时，它们也会面对相同的挑战。
微服务的优势 容忍失败，尽管外部失败后仍可工作。
快速迭代，一次性服务，可独立部署服务。
微服务的反模式 微服务器之间需要负载平衡器
启动顺序很重要
负载平衡无处不在。
Autopilot 模式 可以通过单击来部署和扩展的应用程序。
应用和工作流在我们的笔记本电脑和在云（公有或者私有云）上同样工作
应用和工作流不用强绑在任何特定的架构或者调度上。
Autopilot 应用 Autopilot 模式的解决方案 可以通过 Container 获取服务 Autopilot 实践 应用程序由编写的 docker 容易组成 服务探索可以用过 consul 或者其他 catalog Container 本地健康和服务相应于服务依赖的变化 ContainerPilot 自动化一个 Container 的服务探索，生命周期管理和遥测报告 功能 Container-local 健康检查 PID 1初始化进程 服务探索和注册和观察 遥测报告给 Prometheus 免费以及开源https://github.</description></item><item><title>JSConf China 2017 Day One — Change The World</title><link>https://new.halfrost.com/zh/jsconf_china_2017/</link><pubDate>Sat, 15 Jul 2017 06:21:00 +0000</pubDate><guid>https://new.halfrost.com/zh/jsconf_china_2017/</guid><description>今天有幸参加了 JSConf China 2017 ，作为大会第一天，我来谈谈个人对大会的一些感谢。至于大会讲的更加详细的内容可以直接翻到本文末尾，我和另外一个位前端小伙伴一起写的非常详细的笔记，版权在掘金，感兴趣的可以点链接去看看。
第一场 Programming the Universal Future with next.js 第一场是来自 ZEIT 的大神，讲的是 next.js。
现场演示了 React 是如何利用 next.js 进行服务器端渲染的。
next.js 支持 static projects、package.json(node 项目)、Dockerfile 项目配置一键部署。
开发中常见的需求：自定义 URL，服务端渲染、实时日志，这些也都只需要一个 next 命令就可以搞定！
现场演示了经过 next.js 改造之后的服务器端渲染的性能，页面首屏直接秒开，再也没有了 loading 半天的情况了。用户体验极佳。除了这个以后还演示了懒加载 React 组件。React 组件可以按需加载，再也不用一开始加载所有组件了，这样提高了很多性能。演示中还展示了 next.js 的热加载的功能，hot-reload ，极大的提高了开发效率。
第二场 理解现代 Web 开发 这个演讲提到了太多的话题了。而且演讲过程中语速非常快，keynote 一页一页的。涉及的点实在是广，据说前端没有五到十年经验是无法领悟到其中的精髓的。
这一讲讲师放出了PPT，很值得大家去学习，链接在这里《理解现代 Web 开发》
关于讲师的 GitHub 上还有一个《现代 Web 开发者的魔法书 Spellbook of Modern Web Dev》 同样非常推荐阅读。
1. 如何看待开发的变化 未来的开发形式走向(移动化 &amp;mdash;-&amp;gt; AI 时代 &amp;mdash;-&amp;gt; XXX时代的前夜) 这些问题都比较值得我们深思。</description></item><item><title>大话大前端时代(一) —— Vue 与 iOS 的组件化</title><link>https://new.halfrost.com/zh/vue_ios_modularization/</link><pubDate>Sat, 08 Jul 2017 09:51:00 +0000</pubDate><guid>https://new.halfrost.com/zh/vue_ios_modularization/</guid><description>序 今年大前端的概念一而再再而三的被提及，那么大前端时代究竟是什么呢？大前端这个词最早是因为在阿里内部有很多前端开发人员既写前端又写 Java 的 Velocity 模板而得来，不过现在大前端的范围已经越来越大了，包含前端 + 移动端，前端、CDN、Nginx、Node、Hybrid、Weex、React Native、Native App。笔者是一名普通的全职 iOS 开发者，在接触到了前端开发以后，发现了前端有些值得移动端学习的地方，于是便有了这个大前端时代系列的文章，希望两者能相互借鉴优秀的思想。谈及到大前端，常常被提及的话题有：组件化，路由与解耦，工程化（打包工具，脚手架，包管理工具），MVC 和 MVVM 架构，埋点和性能监控。笔者就先从组件化方面谈起。网上关于前端框架对比的文章也非常多（对比 React，Vue，Angular），不过跨端对比的文章好像不多？笔者就打算以前端和移动端（以 iOS 平台为主）对比为主，看看这两端的不同做法，并讨论讨论有无相互借鉴学习的地方。
本文前端的部分也许前端大神看了会觉得比较基础，如有错误还请各位大神不吝赐教。
Vue 篇 一. 组件化的需求 为了提高代码复用性，减少重复性的开发，我们就把相关的代码按照 template、style、script 拆分，封装成一个个的组件。组件可以扩展 HTML 元素，封装可重用的 HTML 代码，我们可以将组件看作自定义的 HTML 元素。在 Vue 里面，每个封装好的组件可以看成一个个的 ViewModel。
二. 如何封装组件 谈到如何封装的问题，就要先说说怎么去组织组件的问题。
如果在简单的 SPA 项目中，可以直接用 Vue.component 去定义一个全局组件，项目一旦复杂以后，就会出现弊端了：
全局定义(Global definitions) 强制要求每个 component 中的命名不得重复 字符串模板(String templates) 缺乏语法高亮，在 HTML 有多行的时候，需要用到丑陋的 \ 不支持 CSS(No CSS support) 意味着当 HTML 和 JavaScript 组件化时，CSS 明显被遗漏 没有构建步骤(No build step) 限制只能使用 HTML 和 ES5 JavaScript, 而不能使用预处理器，如 Pug (formerly Jade) 和 Babel 而且现在公司级的项目，大多数都会引入工程化的管理，用包管理工具去管理，npm 或者 yarn。所以 Vue 在复杂的项目中用 Vue.</description></item><item><title>揭开 this &amp; that 之迷</title><link>https://new.halfrost.com/zh/javascript_this/</link><pubDate>Sat, 24 Jun 2017 10:52:00 +0000</pubDate><guid>https://new.halfrost.com/zh/javascript_this/</guid><description>新手在入门 JavaScript 的过程中，一定会踩很多关于 this 的坑，出现问题的本质就是 this 指针的指向和自己想的不一样。笔者在入门学习的过程中，也踩了很多坑，于是便写下本篇文章记录自己“踩坑”历程。
一. this 在哪里？ 在上篇《从 JavaScript 作用域说开去》分析中，我们知道，在 Execution Context 中有一个属性是 this，这里的 this 就是我们所说的 this 。this 与上下文中可执行代码的类型有直接关系，this 的值在进入执行上下文时确定，并且在执行上下文运行期间永久不变。
this 到底取何值？this 的取值是动态的，是在函数真正被调用执行的时候确定的，函数定义的时候确定不了。因为 this 的取值是执行上下文环境的一部分，每次调用函数，都会产生一个新的执行上下文环境。
所以 this 的作用就是用来指明执行上下文是在哪个上下文中被触发的对象。令人迷惑的地方就在这里，同一个函数，当在不同的上下文进行调用的时候，this 的值就可能会不同。也就是说，this 的值就是函数调用表达式（也就是函数被调用的方式）的 caller。
二. this &amp;amp; that 具体值得是谁？ 目前接触的有以下14种情况，笔者打算一一列举出来，以后如果遇到了更多的情况，还会继续增加。
既然 this 是执行上下文确定的，那么从执行上下文的种类进行分类，可以分为3种：
那么接下来我们就从 Global Execution Context 全局执行上下文，Function Execution Context 函数执行上下文，Eval Execution Context Eval执行上下文 这三类，具体谈谈 this 究竟指的是谁。
（一）. 全局执行上下文 1. 非严格模式下的函数调用 这是函数的最通常用法，属于全局性调用，因此 this 就代表全局对象 Global。
var name = &amp;#39;halfrost&amp;#39;; function test() { console.</description></item><item><title>Vue 全家桶 + Electron 开发的一个跨三端的应用</title><link>https://new.halfrost.com/zh/vue_electron/</link><pubDate>Sat, 17 Jun 2017 09:50:00 +0000</pubDate><guid>https://new.halfrost.com/zh/vue_electron/</guid><description>利用 Vue.js 实现 objc中国 的跨平台全栈应用
✅ 桌面应用，支持 Mac、Linux、Windows 三个平台 ✅ Web 应用，支持 桌面浏览器 和 手机浏览器 ✅ 手机 App，目前只支持了 Cordova 框架，支持 iOS、Android、Windows Phone、BlackBerry 四个平台 ❌ 手机原生 App，打算用 Weex 框架，同样一起支持 iOS 和 Android 两个平台 注：此项目纯属个人瞎搞，请大家支持 喵神(@onevcat)，支持 Objc中国。
前言 一.关于我 我是一名全职的 iOS 开发者，非前端开发者。由于接触了 Weex 开发，从而接触到了 Vue.js。
二.为什么会写这个项目？ 最开始有这个想法的时候是来自一个网友，他在我的博客上问我，网上有没有写的比较好的 Weex demo ？我说尤大写的那个 Hacker News 是最好的。后来网友就说，楼主能写一个么？我当时回答暂时不行。其实这事我一直记在心里。
今年5月19号，GitHub 使用 Electron 重写了 macOS 和 Windows 的客户端，加上近些年跨端开发越来越火，对于一些公司来说，Web 和 app 应该都是需要的，app 还需要 iOS 和 Android 两个平台，再有甚者还要开发小程序，桌面级的应用虽然少，但是用 Electron 一样可以一起开发了。自己也萌生了想要跃跃欲试的念头。</description></item><item><title>从 JavaScript 作用域说开去</title><link>https://new.halfrost.com/zh/javascript_scope/</link><pubDate>Thu, 25 May 2017 00:16:00 +0000</pubDate><guid>https://new.halfrost.com/zh/javascript_scope/</guid><description>目录 1.静态作用域与动态作用域 2.变量的作用域 3.JavaScript 中变量的作用域 4.JavaScript 欺骗作用域 5.JavaScript 执行上下文 6.JavaScript 中的作用域链 7.JavaScript 中的闭包 8.JavaScript 中的模块 一. 静态作用域与动态作用域 在电脑程序设计中，作用域（scope，或译作有效范围）是名字（name）与实体（entity）的绑定（binding）保持有效的那部分计算机程序。不同的编程语言可能有不同的作用域和名字解析。而同一语言内也可能存在多种作用域，随实体的类型变化而不同。作用域类别影响变量的绑定方式，根据语言使用静态作用域还是动态作用域变量的取值可能会有不同的结果。
包含标识符的宣告或定义； 包含语句和/或表达式，定义或部分关于可运行的算法； 嵌套嵌套或被嵌套嵌套。 名字空间是一种作用域，使用作用域的封装性质去逻辑上组群起关相的众识别子于单一识别子之下。因此，作用域可以影响这些内容的名字解析。 程序员常会缩进他们的源代码中的作用域，改善可读性。
作用域又分为两种，静态作用域和动态作用域。
静态作用域又叫做词法作用域，采用词法作用域的变量叫词法变量。词法变量有一个在编译时静态确定的作用域。词法变量的作用域可以是一个函数或一段代码，该变量在这段代码区域内可见（visibility）；在这段区域以外该变量不可见（或无法访问）。词法作用域里，取变量的值时，会检查函数定义时的文本环境，捕捉函数定义时对该变量的绑定。
function f() { function g() { } } 静态(词法)作用域，就是可以无须执行程序而只从程序源码的角度，就可以看出程序是如何工作的。从上面的例子中可以肯定，函数 g 是被函数 f 包围在内部。
大多数现在程序设计语言都是采用静态作用域规则，如C/C++、C#、Python、Java、JavaScript……
相反，采用动态作用域的变量叫做动态变量。只要程序正在执行定义了动态变量的代码段，那么在这段时间内，该变量一直存在；代码段执行结束，该变量便消失。这意味着如果有个函数f，里面调用了函数g，那么在执行g的时候，f里的所有局部变量都会被g访问到。而在静态作用域的情况下，g不能访问f的变量。动态作用域里，取变量的值时，会由内向外逐层检查函数的调用链，并打印第一次遇到的那个绑定的值。显然，最外层的绑定即是全局状态下的那个值。
function g() { } function f() { g()； } 当我们调用f()，它会调用g()。在执行期间，g被f调用代表了一种动态的关系。
采用动态作用域的语言有Pascal、Emacs Lisp、Common Lisp（兼有静态作用域）、Perl（兼有静态作用域）。C/C++是静态作用域语言，但在宏中用到的名字，也是动态作用域。
二. 变量的作用域 1. 变量的作用域 变量的作用域是指变量在何处可以被访问到。比如：
function foo（）{ var bar; } 这里的 bar 的直接作用域是函数作用域foo()；</description></item><item><title>JavaScript 新手的踩坑日记</title><link>https://new.halfrost.com/zh/lost_in_javascript/</link><pubDate>Fri, 12 May 2017 22:18:00 +0000</pubDate><guid>https://new.halfrost.com/zh/lost_in_javascript/</guid><description>引语 在1995年5月，Eich 大神在10天内就写出了第一个脚本语言的版本，JavaScript 的第一个代号是 Mocha，Marc Andreesen 起的这个名字。由于商标问题以及很多产品已经使用了 Live 的前缀，网景市场部将它改名为 LiveScript。在1995年11月底，Navigator 2.0B3 发行，其中包含了该语言的原型，这个版本相比之前没有什么大的变化。在1995年12月初，Java 语言发展壮大，Sun 把 Java 的商标授权给了网景。这个语言被再次改名，变成了最终的名字——JavaScript。在之后的1997年1月，标准化以后，就成为现在的 ECMAScript。
近一两年在客户端上用到 JS 的地方也越来越多了，笔者最近接触了一下 JS ，作为前端小白，记录一下近期自己“踩坑”的成长经历。
一. 原始值和对象 在 JavaScript 中，对值的区分就两种：
1.原始值：BOOL，Number，String，null，undefined。 2.对象：每个对象都有唯一的标识且只严格的等于( = = = ) 自己。
null，undefined没有属性，连toString( )方法也没有。
false，0，NaN，undefined，null，&amp;rsquo; ' ，都是false。
typeof 运算符能区分原始值和对象，并检测出原始值的类型。 instanceof 运算符可以检测出一个对象是否是特定构造函数的一个实例或者是否为它的一个子类。
引用类型： 对象、数组、函数
剩下的类型都为 值类型
null 返回的是一个 object，这个是一个不可修复的 bug，如果修改这个 bug，就会破坏现有代码体系。但是这不能表示 null 是一个对象。
因为第一代 JavaScript 引擎中的 JavaScript 值表示为32位的字符。最低3位作为一种标识，表示值是对象，整数，浮点数或者布尔值。对象的标识是000，而为了表现 null ，引擎使用了机器语言 NULL 的指针，该字符的所有位都是0。而 typeof 就是检测值的标志位，这就是为什么它会认为 null 是一个对象了。
所以判断 一个 value 是不是一个对象应该按照如下条件判断：</description></item><item><title>iOS 开发者的 Weex 伪最佳实践指北</title><link>https://new.halfrost.com/zh/weex_best_practice_guidelines/</link><pubDate>Wed, 03 May 2017 06:07:00 +0000</pubDate><guid>https://new.halfrost.com/zh/weex_best_practice_guidelines/</guid><description>引子 这篇文章是笔者近期关于Weex在iOS端的一些研究和实践心得，和大家一起分享分享，也算是对学习成果的总结。文章里面提到的做法也许不是最佳实践，也许里面的方法称不算是一份标准的指南手册，所以标题就只好叫“伪最佳实践指北”了。有更好的方法欢迎大家一起留言讨论，一起学习。
由于笔者不太了解Android，所以以下的文章不会涉及到Android。
一. React Native 和 Weex 自从Weex出生的那一天起，就无法摆脱和React Native相互比较的命运。React Native宣称“Learn once, write anywhere”，而Weex宣称“Write Once, Run Everywhere”。Weex从出生那天起，就被给予了一统三端的厚望。React Native可以支持iOS、Android，而Weex可以支持iOS、Android、HTML5。
在Native端，两者的最大的区别可能就是在对JSBundle是否分包。React Native官方只允许将React Native基础JS库和业务JS一起打成一个JS bundle，没有提供分包的功能，所以如果想节约流量就必须制作分包打包工具。而Weex默认打的JS bundle只包含业务JS代码，体积小很多，基础JS库包含在Weex SDK中，这一点Weex与Facebook的React Native和微软的Cordova相比，Weex更加轻量，体积小巧。
在JS端，Weex又被人称为Vue Native，所以 React Native 和 Weex 的区别就在 React 和 Vue 两者上了。
笔者没有写过React Native，所以也没法客观的去比较两者。不过知乎上有一个关于Weex 和 React Native很好的对比文章《weex&amp;amp;React Native对比》，推荐大家阅读。
前两天@Allen 许帅也在Glow 技术团队博客上面发布了一篇《React Native 在 Glow 的实践》这篇文章里面也谈了很多关于React Native实践相关的点，也强烈推荐大家去阅读。
二. 入门基础 关于小白想入门Weex，当然最基础的还是要通读文档，文档是官方最好的学习资料。官方的基础文档有两份：
教程文档
手册文档
在文档手册里面包含了Weex所有目前有的组件，模块，每个组件和模块的用法和属性。遇到问题可以先过来翻翻。很有可能有些组件和模块没有那些属性。
1. Weex全家桶和脚手架 看完官方文档以后，就可以开始上手构建工程项目了。
我司在知乎上面写了4篇关于《Weex入坑指南的》。这四篇文章还是很值得看的。
Weex也和前端项目一样，拥有它自己的脚手架全家桶。weex-toolkit + weexpack + playground + code snippets + weex-devtool。</description></item><item><title>Weex 中别具匠心的 JS Framework</title><link>https://new.halfrost.com/zh/weex_js_framework/</link><pubDate>Sun, 23 Apr 2017 05:15:00 +0000</pubDate><guid>https://new.halfrost.com/zh/weex_js_framework/</guid><description>前言 Weex为了提高Native的极致性能，做了很多优化的工作
为了达到所有页面在用户端达到秒开，也就是网络（JS Bundle下载）和首屏渲染（展现在用户第一屏的渲染时间）时间和小于1s。
手淘团队在对Weex进行性能优化时，遇到了很多问题和挑战：
JS Bundle下载慢，压缩后60k左右大小的JS Bundle，在全网环境下，平均下载速度大于800ms（在2G/3G下甚至是2s以上）。 JS和Native通信效率低，拖慢了首屏加载时间。
最终想到的办法就是把JSFramework内置到SDK中，达到极致优化的作用。
客户端访问Weex页面时，首先会网络请求JS Bundle，JS Bundle被加载到客户端本地后，传入JSFramework中进行解析渲染。JS Framework解析和渲染的过程其实是根据JS Bundle的数据结构创建Virtual DOM 和数据绑定，然后传递给客户端渲染。
由于JSFramework在本地，所以就减少了JS Bundle的体积，每个JS Bundle都可以减少一部分体积，Bundle里面只保留业务代码。每个页面下载Bundle的时间都可以节约10-20ms。如果Weex页面非常多，那么每个页面累计起来节约的时间就很多了。 Weex这种默认就拆包加载的设计，比ReactNative强，也就不需要考虑一直困扰ReactNative头疼的拆包的问题了。
整个过程中，JSFramework将整个页面的渲染分拆成一个个渲染指令，然后通过JS Bridge发送给各个平台的RenderEngine进行Native渲染。因此，尽管在开发时写的是 HTML / CSS / JS，但最后在各个移动端（在iOS上对应的是iOS的Native UI、在Android上对应的是Android的Native UI）渲染后产生的结果是纯Native页面。 由于JSFramework在本地SDK中，只用在初始化的时候初始化一次，之后每个页面都无须再初始化了。也进一步的提高了与Native的通信效率。
JSFramework在客户端的作用在前几篇文章里面也提到了。它的在Native端的职责有3个：
管理每个Weex instance实例的生命周期。 不断的接收Native传过来的JS Bundle，转换成Virtual DOM，再调用Native的方法，构建页面布局。 响应Native传过来的事件，进行响应。 接下来，笔者从源码的角度详细分析一下Weex 中别具匠心的JS Framework是如何实现上述的特性的。
目录 1.Weex JS Framework 初始化 2.Weex JS Framework 管理实例的生命周期 3.Weex JS Framework 构建Virtual DOM 4.Weex JS Framework 处理Native触发的事件 5.Weex JS Framework 未来可能做更多的事情 一.</description></item><item><title>Weex 事件传递的那些事儿</title><link>https://new.halfrost.com/zh/weex_event/</link><pubDate>Fri, 14 Apr 2017 18:18:00 +0000</pubDate><guid>https://new.halfrost.com/zh/weex_event/</guid><description>前言 在前两篇文章里面分别谈了Weex如何在Native端初始化的和Weex是如何高效的渲染Native的原生UI的。Native这边还缺一块，那就是Native产生的一些事件，是怎么传回给JS的。这篇文章就详细分析这一部分。
目录 1.Weex的事件类型 2.Weex的事件传递 一.Weex的事件类型 在Weex中，目前最新版本中事件总共分为4种类型，通用事件，Appear 事件，Disappear 事件，Page 事件。
在Weex的组件里面只包含前三种事件，即通用事件，Appear 事件，Disappear 事件。
当WXComponent添加事件的时候，会调用以下函数：
- (void)_addEventOnMainThread:(NSString *)addEventName { WX_ADD_EVENT(appear, addAppearEvent) WX_ADD_EVENT(disappear, addDisappearEvent) WX_ADD_EVENT(click, addClickEvent) WX_ADD_EVENT(swipe, addSwipeEvent) WX_ADD_EVENT(longpress, addLongPressEvent) WX_ADD_EVENT(panstart, addPanStartEvent) WX_ADD_EVENT(panmove, addPanMoveEvent) WX_ADD_EVENT(panend, addPanEndEvent) WX_ADD_EVENT(horizontalpan, addHorizontalPanEvent) WX_ADD_EVENT(verticalpan, addVerticalPanEvent) WX_ADD_EVENT(touchstart, addTouchStartEvent) WX_ADD_EVENT(touchmove, addTouchMoveEvent) WX_ADD_EVENT(touchend, addTouchEndEvent) WX_ADD_EVENT(touchcancel, addTouchCancelEvent) [self addEvent:addEventName]; } WX_ADD_EVENT是一个宏：
#define WX_ADD_EVENT(eventName, addSelector) \ if ([addEventName isEqualToString:@#eventName]) {\ [self addSelector];\ } 即是判断待添加的事件addEventName的名字和默认支持的事件名字eventName是否一致，如果一致，就执行addSelector方法。
最后会执行一个addEvent:方法，每个组件里面会可以重写这个方法。在这个方法里面做的就是对组件的状态的标识。
比如WXWebComponent组件里面的addEvent:方法：
- (void)addEvent:(NSString *)eventName { if ([eventName isEqualToString:@&amp;#34;pagestart&amp;#34;]) { _startLoadEvent = YES; } else if ([eventName isEqualToString:@&amp;#34;pagefinish&amp;#34;]) { _finishLoadEvent = YES; } else if ([eventName isEqualToString:@&amp;#34;error&amp;#34;]) { _failLoadEvent = YES; } } 在这个方法里面即对Web组件里面的状态进行了标识。</description></item><item><title>由 FlexBox 算法强力驱动的 Weex 布局引擎</title><link>https://new.halfrost.com/zh/weex_flexbox/</link><pubDate>Fri, 31 Mar 2017 08:26:00 +0000</pubDate><guid>https://new.halfrost.com/zh/weex_flexbox/</guid><description>前言 在上篇文章里面谈了Weex在iOS客户端工作的基本流程。这篇文章将会详细的分析Weex是如何高性能的布局原生界面的，之后还会与现有的布局方法进行对比，看看Weex的布局性能究竟如何。
目录 1.Weex布局算法 2.Weex布局算法性能分析 3.Weex是如何布局原生界面的 一. Weex布局算法 打开Weex的源码的Layout文件夹，就会看到两个c的文件，这两个文件就是今天要谈的Weex的布局引擎。
Layout.h和Layout.c最开始是来自于React-Native里面的代码。也就是说Weex和React-Native的布局引擎都是同一套代码。
当前React-Native的代码里面已经没有这两个文件了，而是换成了Yoga。
Yoga本是Facebook在React Native里引入的一种跨平台的基于CSS的布局引擎，它实现了Flexbox规范，完全遵守W3C的规范。随着该系统不断完善，Facebook对其进行重新发布，于是就成了现在的Yoga(Yoga官网)。
那么Flexbox是什么呢？
熟悉前端的同学一定很熟悉这个概念。2009年，W3C提出了一种新的方案——Flex布局，可以简便、完整、响应式地实现各种页面布局。目前，它已经得到了几乎所有浏览器的支持，目前的前端主要是使用Html / CSS / JS实现，其中CSS用于前端的布局。任何一个Html的容器可以通过css指定为Flex布局，一旦一个容器被指定为Flex布局，其子元素就可以按照FlexBox的语法进行布局。
关于FlexBox的基本定义，更加详细的文档说明，感兴趣的同学可以去阅读一下W3C的官方文档，那里会有很详细的说明。官方文档链接
Weex中的Layout文件是Yoga的前身，是Yoga正式发布之前的版本。底层代码使用C语言代码，所以性能也不是问题。接下来就仔细分析Layout文件是如何实现FlexBox的。
故以下源码分析都基于v0.10.0这个版本。
（一）FlexBox中的基本数据结构 Flexbox布局（Flexible Box)设计之初的目的是为了能更加高效的分配子视图的布局情况，包括动态的改变宽度，高度，以及排列顺序。Flexbox可以更加方便的兼容各个大小不同的屏幕，比如拉伸和压缩子视图。
在FlexBox的世界里，存在着主轴和侧轴的概念。
大多数情况，子视图都是沿着主轴（main axis），从主轴起点（main-start）到主轴终点（main-end）排列。但是这里需要注意的一点是，主轴和侧轴虽然永远是垂直的关系，但是谁是水平，谁是竖直，并没有确定，有可能会有如下的情况：
在上图这种水平是侧轴的情况下，子视图是沿着侧轴（cross axis），从侧轴起点（cross-start）到侧轴终点（cross-end）排列的。
**主轴（main axis）：**父视图的主轴，子视图主要沿着这条轴进行排列布局。
**主轴起点（main-start）和主轴终点（main-end）：**子视图在父视图里面布局的方向是从主轴起点（main-start）向主轴终点（main-start）的方向。
**主轴尺寸（main size）：**子视图在主轴方向的宽度或高度就是主轴的尺寸。子视图主要的大小属性要么是宽度，要么是高度属性，由哪一个对着主轴方向决定。
**侧轴（cross axis）：**垂直于主轴称为侧轴。它的方向主要取决于主轴方向。
**侧轴起点（cross-start）和侧轴终点（cross-end）：**子视图行的配置从容器的侧轴起点边开始，往侧轴终点边结束。
**侧轴尺寸（cross size）：**子视图的在侧轴方向的宽度或高度就是项目的侧轴长度，伸缩项目的侧轴长度属性是「width」或「height」属性，由哪一个对着侧轴方向决定。
接下来看看Layout是怎么定义FlexBox里面的元素的。
typedef enum { CSS_DIRECTION_INHERIT = 0, CSS_DIRECTION_LTR, CSS_DIRECTION_RTL } css_direction_t; 这个方向是定义的上下文的整体布局的方向，INHERIT是继承，LTR是Left To Right，从左到右布局。RTL是Right To Left，从右到左布局。下面分析如果不做特殊说明，都是LTR从左向右布局。如果是RTL就是LTR反向。
typedef enum { CSS_FLEX_DIRECTION_COLUMN = 0, CSS_FLEX_DIRECTION_COLUMN_REVERSE, CSS_FLEX_DIRECTION_ROW, CSS_FLEX_DIRECTION_ROW_REVERSE } css_flex_direction_t; 这里定义的是Flex的方向。</description></item><item><title>Weex 是如何在 iOS 客户端上跑起来的</title><link>https://new.halfrost.com/zh/weex_ios/</link><pubDate>Sat, 18 Mar 2017 02:29:00 +0000</pubDate><guid>https://new.halfrost.com/zh/weex_ios/</guid><description>前言 2016年4月21日，阿里巴巴在Qcon大会上宣布跨平台移动开发工具Weex开放内测邀请。Weex能够完美兼顾性能与动态性，让移动开发者通过简捷的前端语法写出Native级别的性能体验，并支持iOS、安卓、YunOS及Web等多端部署。
近一年来，ReactNative 和 Weex 这些跨平台技术对Native开发者来说，冲击是巨大的。Native在开发App的时候存在一些弊端，比如客户端需要频繁更新，iOS更新时间还要受到审核的牵制；iOS、Android和前端同时开发同一个需求，在人员成本上消耗大；Hybrid的性能和Native相比又差了一点。
ReactNative 和 Weex的出现，就是为了解决这些痛点的。
从4月21号宣布内测以后，短短两周就有超过5000名开发者申请。
2016年6月30日阿里巴巴正式宣布Weex开源。号称可以用Web方式，开发Native级性能体验的亿级应用匠心打造跨平台移动开发工具Weex在开源首日就登上Github趋势榜首位，截止目前为止，Weex在GitHub上的Star数已经到达了13393了。成为中国2016年在Github上最热门的开源项目之一。
目录 1.Weex概述 2.Weex工作原理 3.Weex在iOS上是如何跑起来的 4.关于Weex，ReactNative，JSPatch 一. Weex概述 Weex从出生那天起，仿佛就是和ReactNative是“一对”。
ReactNative宣称“Learn once, write anywhere”，而Weex宣称“Write Once, Run Everywhere”。Weex从出生那天起，就被给予了一统三端的厚望。ReactNative可以支持iOS、Android，而Weex可以支持iOS、Android、HTML5。一统三端就解决了前言里面说的第二个痛点，同时开发浪费人员成本的问题。
Native移动开发者只需要在本地导入Weex的SDK，就可以通过HTML/CSS/JavaScript网页的这套编程语言来开发Native级别的Weex界面。这意味着可以直接用现有Web开发的编辑器和IDE的代码补全、提示、检查等功能。从而也给前端人员开发Native端，较低的开发成本和学习成本。
Weex是一种轻量级、可扩展、高性能框架。集成也很方便，可以直接在HTML5页面嵌入，也可嵌在原生UI中。由于和ReactNative一样，都会调用Native端的原生控件，所以在性能上比Hybrid高出一个层次。这就解决了前言里面所说的第三个痛点，性能问题。
Weex非常轻量，体积小巧，语法简单，方便接入和上手。ReactNative官方只允许将ReactNative基础JS库和业务JS一起打成一个JS bundle，没有提供分包的功能，所以如果想节约流量就必须制作分包打包工具。而Weex默认打的JS bundle只包含业务JS代码，体积小很多，基础JS库包含在Weex SDK中，这一点Weex与Facebook的React Native和微软的Cordova相比，Weex更加轻量，体积小巧。把Weex生成的JS bundle轻松部署到服务器端，然后Push到客户端，或者客户端请求新的资源即可完成发布。如此快速的迭代就解决了前言里面说的第一个痛点，发布无法控制时间，
Weex中Native组件和API都可以横向扩展，业务方可去中心化横向灵活化定制组件和功能模块。并且还可以直接复用Web前端的工程化管理和监控性能等工具。
知乎上有一个关于Weex 和 ReactNative很好的对比文章weex&amp;amp;ReactNative对比，推荐大家阅读。
Weex在2017年2月17日正式发布v0.10.0，这个里程碑的版本开始完美的兼容Vue.js开发Weex界面。
Weex又于2017年2月24 迁移至 Apache 基金会，阿里巴巴会基于 Apache 的基础设施继续迭代。并启用了全新的 GitHub 仓库：https://github.com/apache/incubator-weex
故以下源码分析都基于v0.10.0这个版本。
二. Weex工作原理 上图是官方给的一张原理图，Weex是如何把JS打包成JS Bundle的原理本篇文章暂时不涉及。本篇文章会详细分析Weex是如何在Native端工作的。笔者把Native端的原理再次细分，如下图：
Weex可以通过自己设计的DSL，书写.we文件或者.vue文件来开发界面，整个页面书写分成了3段，template、style、script，借鉴了成熟的MVVM的思想。
Weex在性能方面，为了尽可能的提升客户端的性能，DSL的Transformer全部都放在了服务器端实现，Weex会在服务器端将XML + CSS + JavaScript 代码全部都转换成JS Bundle。服务器将JS Bundle部署到Server上和CDN上。
Weex和React Native不同的是，Weex把JS Framework内置在SDK里面，用来解析从服务器上下载的JS Bundle，这样也减少了每个JS Bundle的体积，不再有React Native需要分包的问题。客户端请求完JS Bundle以后，传给JS Framework，JS Framework解析完成以后会输出Json格式的Virtual DOM，客户端Native只需要专心负责 Virtual DOM 的解析和布局、UI 渲染。然而这一套解析，布局，渲染的逻辑SDK基本实现了。</description></item><item><title>BeeHive —— 一个优雅但还在完善中的解耦框架</title><link>https://new.halfrost.com/zh/beehive/</link><pubDate>Sat, 04 Mar 2017 10:44:00 +0000</pubDate><guid>https://new.halfrost.com/zh/beehive/</guid><description>前言 BeeHive是阿里巴巴公司开源的一个iOS框架，这个框架是App模块化编程的框架一种实现方案，吸收了Spring框架Service的理念来实现模块间的API解耦。
BeeHive这个名字灵感来源于蜂窝。蜂窝是世界上高度模块化的工程结构，六边形的设计能带来无限扩张的可能。所以就用了这个名字作为开源项目的名字。
在前一篇文章iOS 组件化 —— 路由设计思路分析中，我们分析了App组件之间可以通过路由来解除耦合。那么这篇文章就来看看利用模块化的思想如何解除耦合的。
(看到这里一定会很多人有疑问，那就看看这篇文章组件和模块的区别)
说明：本文是基于BeeHive v1.2.0版本进行解析的。
目录 1.BeeHive概述 2.BeeHive模块注册 3.BeeHive模块事件 4.BeeHive模块调用 5.其他的一些辅助类 6.可能还在完善中的功能 一. BeeHive概述 由于BeeHive是基于Spring的Service理念，虽然可以使模块间的具体实现与接口解耦，但无法避免模块对接口类的依赖关系。
暂时BeeHive没有采用invoke和performSelector:action withObject: params的方法。主要原因还是考虑学习成本难度以及动态调用实现无法在编译检查阶段检测接口参数变更等问题。
目前BeeHive v1.2.0 全部是利用Protocol的方式，实现了模块间解耦的目的：
1.各个模块以插件的形式存在。每个都可独立，相互解耦。 2.各个模块具体实现与接口调用分离 3.各个模块也有生命周期，也可以进行管理。
官方也给出了一个架构图：
接下来就依次分析模块注册，模块事件，模块调用是如何实现解耦的。
二. BeeHive模块注册 先从模块的注册开始分析，来看看BeeHive是如何给各个模块进行注册的。
在BeeHive中是通过BHModuleManager来管理各个模块的。BHModuleManager中只会管理已经被注册过的模块。
注册Module的方式总共有三种：
1. Annotation方式注册 通过BeeHiveMod宏进行Annotation标记。
BeeHiveMod(ShopModule) BeeHiveMod宏定义如下：
#define BeeHiveMod(name) \ char * k##name##_mod BeeHiveDATA(BeehiveMods) = &amp;#34;&amp;#34;#name&amp;#34;&amp;#34;; BeeHiveDATA又是一个宏：
#define BeeHiveDATA(sectname) __attribute((used, section(&amp;#34;__DATA,&amp;#34;#sectname&amp;#34; &amp;#34;))) 最终BeeHiveMod宏会在预编译结束会完全展开成下面的样子：
char * kShopModule_mod __attribute((used, section(&amp;#34;__DATA,&amp;#34;&amp;#34;BeehiveMods&amp;#34;&amp;#34; &amp;#34;))) = &amp;#34;&amp;#34;&amp;#34;ShopModule&amp;#34;&amp;#34;&amp;#34;; 注意双引号的总对数。</description></item><item><title>iOS 组件化 —— 路由设计思路分析</title><link>https://new.halfrost.com/zh/ios_router/</link><pubDate>Sat, 25 Feb 2017 03:39:00 +0000</pubDate><guid>https://new.halfrost.com/zh/ios_router/</guid><description>前言 随着用户的需求越来越多，对App的用户体验也变的要求越来越高。为了更好的应对各种需求，开发人员从软件工程的角度，将App架构由原来简单的MVC变成MVVM，VIPER等复杂架构。更换适合业务的架构，是为了后期能更好的维护项目。
但是用户依旧不满意，继续对开发人员提出了更多更高的要求，不仅需要高质量的用户体验，还要求快速迭代，最好一天出一个新功能，而且用户还要求不更新就能体验到新功能。为了满足用户需求，于是开发人员就用H5，ReactNative，Weex等技术对已有的项目进行改造。项目架构也变得更加的复杂，纵向的会进行分层，网络层，UI层，数据持久层。每一层横向的也会根据业务进行组件化。尽管这样做了以后会让开发更加有效率，更加好维护，但是如何解耦各层，解耦各个界面和各个组件，降低各个组件之间的耦合度，如何能让整个系统不管多么复杂的情况下都能保持“高内聚，低耦合”的特点？这一系列的问题都摆在开发人员面前，亟待解决。今天就来谈谈解决这个问题的一些思路。
目录 1.引子 2.App路由能解决哪些问题 3.App之间跳转实现 4.App内组件间路由设计 5.各个方案优缺点 6.最好的方案 一. 引子 大前端发展这么多年了，相信也一定会遇到相似的问题。近两年SPA发展极其迅猛，React 和 Vue一直处于风口浪尖，那我们就看看他们是如何处理好这一问题的。
在SPA单页面应用，路由起到了很关键的作用。路由的作用主要是保证视图和 URL 的同步。在前端的眼里看来，视图是被看成是资源的一种表现。当用户在页面中进行操作时，应用会在若干个交互状态中切换，路由则可以记录下某些重要的状态，比如用户查看一个网站，用户是否登录、在访问网站的哪一个页面。而这些变化同样会被记录在浏览器的历史中，用户可以通过浏览器的前进、后退按钮切换状态。总的来说，用户可以通过手动输入或者与页面进行交互来改变 URL，然后通过同步或者异步的方式向服务端发送请求获取资源，成功后重新绘制 UI，原理如下图所示：
react-router通过传入的location到最终渲染新的UI，流程如下：
location的来源有2种，一种是浏览器的回退和前进，另外一种是直接点了一个链接。新的 location 对象后，路由内部的 matchRoutes 方法会匹配出 Route 组件树中与当前 location 对象匹配的一个子集，并且得到了 nextState，在this.setState(nextState) 时就可以实现重新渲染 Router 组件。
大前端的做法大概是这样的，我们可以把这些思想借鉴到iOS这边来。上图中的Back / Forward 在iOS这边很多情况下都可以被UINavgation所管理。所以iOS的Router主要处理绿色的那一块。
二. App路由能解决哪些问题 既然前端能在SPA上解决URL和UI的同步问题，那这种思想可以在App上解决哪些问题呢？
思考如下的问题，平时我们开发中是如何优雅的解决的：
1.3D-Touch功能或者点击推送消息，要求外部跳转到App内部一个很深层次的一个界面。
比如微信的3D-Touch可以直接跳转到“我的二维码”。“我的二维码”界面在我的里面的第三级界面。或者再极端一点，产品需求给了更加变态的需求，要求跳转到App内部第十层的界面，怎么处理？
2.自家的一系列App之间如何相互跳转？
如果自己App有几个，相互之间还想相互跳转，怎么处理？
3.如何解除App组件之间和App页面之间的耦合性？
随着项目越来越复杂，各个组件，各个页面之间的跳转逻辑关联性越来越多，如何能优雅的解除各个组件和页面之间的耦合性？
4.如何能统一iOS和Android两端的页面跳转逻辑？甚至如何能统一三端的请求资源的方式？
项目里面某些模块会混合ReactNative，Weex，H5界面，这些界面还会调用Native的界面，以及Native的组件。那么，如何能统一Web端和Native端请求资源的方式？
5.如果使用了动态下发配置文件来配置App的跳转逻辑，那么如果做到iOS和Android两边只要共用一套配置文件？
6.如果App出现bug了，如何不用JSPatch，就能做到简单的热修复功能？
比如App上线突然遇到了紧急bug，能否把页面动态降级成H5，ReactNative，Weex？或者是直接换成一个本地的错误界面？
7.如何在每个组件间调用和页面跳转时都进行埋点统计？每个跳转的地方都手写代码埋点？利用Runtime AOP ？
8.如何在每个组件间调用的过程中，加入调用的逻辑检查，令牌机制，配合灰度进行风控逻辑？
9.如何在App任何界面都可以调用同一个界面或者同一个组件？只能在AppDelegate里面注册单例来实现？
比如App出现问题了，用户可能在任何界面，如何随时随地的让用户强制登出？或者强制都跳转到同一个本地的error界面？或者跳转到相应的H5，ReactNative，Weex界面？如何让用户在任何界面，随时随地的弹出一个View ？
以上这些问题其实都可以通过在App端设计一个路由来解决。那么我们怎么设计一个路由呢？
三. App之间跳转实现 在谈App内部的路由之前，先来谈谈在iOS系统间，不同App之间是怎么实现跳转的。
1. URL Scheme方式 iOS系统是默认支持URL Scheme的，具体见官方文档。</description></item><item><title>TCP 进阶</title><link>https://new.halfrost.com/zh/advance_tcp/</link><pubDate>Thu, 16 Feb 2017 11:24:00 +0000</pubDate><guid>https://new.halfrost.com/zh/advance_tcp/</guid><description>一. 端口号 标准的端口号由 Internet 号码分配机构(IANA)分配。这组数字被划分为特定范围，包括 熟知端口号(0 - 1023)、注册端口号(1024 - 49151)和动态/私有端口号(49152 - 65535)。
如果我们测试这些标准服务和其他 TCP/IP 服务(Telnet、 FTP、 SMTP等) 使用的端口号，会发现它们大多数是奇数。这是有历史原困的，这些端口号从 NCP 端口号派生而来(NCP 是网络控制协议，在 TCP 之前作为 ARPANET 的传输层协议)。NCP 虽然简单，但不是全双工的，困此每个应用需要两个连接，并为每个应用保留奇偶成对的端口号。当 TCP 和 UDP 成为标准的传输层协议时，每个应用只需要一个端口号，因此来自 NCP 的奇数端口号被使用。
二. TCP 初始序列号 在 TCP 数据报中，有一个 序列号 (Sequence Number)。如果序列号被人猜出来，就会展现出 TCP 的脆弱性。
如果选择合适的序列号、IP地址以及端口号，那么任何人都能伪造出一个 TCP 报文段，从而 打断 TCP 的正常连接[RFC5961]。一种抵御上述行为的方法是使初始序列号(或者临时端口 号[RFC6056])变得相对难以被猜出，而另一种方法则是加密。
Linux 系统采用一个相对复杂的过程来选择它的初始序列号。它采用基于时钟的方案，并且针对每一个连接为时钟设置随机的偏移量。随机偏移量是在连接标识(由 2 个 IP 地址与 2 个端口号构成的 4 元组，即 4 元组)的基础上利用加密散列函数得到的。散列函数的输人每隔 5 分钟就会改变一次。在 32 位的初始序列号中，最高的 8 位是一个保密的序列号，而剩余的备位则由散列函数生成。上述方法所生成的序列号很难被猜出，但依然会随着时间而逐步增加。据报告显示， Windows 系统使用了一种基于 RC4[S94] 的类似方案。</description></item><item><title>TCP/IP 指南</title><link>https://new.halfrost.com/zh/tcp_ip/</link><pubDate>Wed, 15 Feb 2017 10:31:00 +0000</pubDate><guid>https://new.halfrost.com/zh/tcp_ip/</guid><description>一. OSI 模型 OSI 参考模型终究只是一个“模型”，它也只是对各层的作用做了一系列粗略的界定，并没有对协议和接口进行详细的定义。它对学习和设计协议只能起到一个引导的作用。因此，若想要了解协议的更多细节，还是有必要参考每个协议本身的具体规范。
上图是 《图解 TCP/IP》书上对七层模型的定义。
上图是一些协议在 OSI 模型中的位置。值得注意的是 DNS 是应用层协议，SSL 分别位于第五层会话层和第六层表示层。TLS 位于第五层会话层。（DNS、SSL、TLS 这些协议会在后面详细分析与说明）
上图是 TCP/IP 模型和 OSI 模型的对比图。
接下来放2张网络上的图，笔者对图上的内容持有争议，至于下面2张图哪里对哪里错，欢迎开 issue 讨论。
上面这种图说 DNS 是网络层协议，笔者周围很多朋友都一致认为是应用层协议。还有一个错误是 SSL 是跨第六层和第七层的，这里画的还是不对。
上面这种图中 DNS 位于应用层，这点赞同，并且也画出了 DNS 是基于 UDP 和 TCP 的。这点也非常不错！（至于有些人不知道 DNS 为何也是基于 TCP 的，这点在 DNS 那里详细分析）。但是上图中没有画出 SSL/TLS 是位于第几层的。
笔者认为上面2张图，虽然看上去非常复杂，内容详尽，但是仔细推敲，还是都有不足和不对的地方。
二. OSI 参考模型通信举例 上图是 5 层 TCP/IP 模型中通信时候的数据图。值得说明的一点，在数据链路层的以太网帧里面，除去以太网首部 14 字节，FCS 尾部 4 字节，中间的数据长度在 46-1500 字节之间。
上图是 OSI 7 层模型中通信时候的数据图。从上图的 7 层模型中，物理层传输的是字节流，数据链路层中包的单位叫，帧。IP、TCP、UDP 网络层的包的单位叫，数据报。TCP、UDP 数据流中的信息叫，段。最后，应用层协议中数据的单位叫，消息。</description></item><item><title>HTTP 指南</title><link>https://new.halfrost.com/zh/http/</link><pubDate>Tue, 14 Feb 2017 09:29:00 +0000</pubDate><guid>https://new.halfrost.com/zh/http/</guid><description>Web 使用一种名为 HTTP (HyperText Transfer Protocol，超文本传输协议) 的协议作为规范的。
HTTP 更加严谨的译名应该是 超文本转移协议。
HTTP 于 1990 年问世。那时的 HTTP 并没有作为正式的标准，因为被称为 HTTP/0.9
HTTP 正式作为标准被公布是 1996 年 5 月，版本命名为 HTTP/1.0，记载于 RFC1945
HTTP 在 1997 年 1 月公布了当前最主流的版本，版本命名为 HTTP/1.1，记载于 RFC2616
HTTP/2 于 2015 年 5 月 14 日发布，引入了服务器推送等多种功能，是目前最新的版本。记载于 RFC7540 (它不叫 HTTP/2.0，是因为标准委员会不打算再发布子版本了，下一个新版本将是 HTTP/3)
一. HTTP 支持的方法 HTTP 是一种不保存状态，即 无状态（ stateless ）协议。HTTP 协议自身不对请求和响应之间的通信状态进行保存。也就是说在 HTTP 这个级别，协议对于发送过的请求或响应都不做持久化处理。这也是为了更快的处理大量事务，确保协议的可伸缩性。
HTTP/1.1 虽然是无状态协议，但是为了实现期望的保持状态的功能，特意引入了 Cookie 技术。
在HTTP/1.1规范中幂等性的定义是：
Methods can also have the property of &amp;ldquo;idempotence&amp;rdquo; in that (aside from error or expiration issues) the side-effects of N &amp;gt; 0 identical requests is the same as for a single request.</description></item><item><title>ReactiveCocoa 中 奇妙无比的“宏”魔法</title><link>https://new.halfrost.com/zh/reactivecocoa_macro/</link><pubDate>Sun, 12 Feb 2017 05:46:54 +0000</pubDate><guid>https://new.halfrost.com/zh/reactivecocoa_macro/</guid><description>前言 在ReactiveCocoa 中，开源库作者为我们提供了很多种魔法，“黑”魔法，“红”魔法……今天就让先来看看“红”魔法。
在ReactiveCocoa 中，封装了很多非常实用的“宏”，使用这些“宏”为我们开发带来了很多的便利。
今天就来盘点一下RAC中的宏是如何实现的。
目录 1.关于宏 2.ReactiveCocoa 中的元宏 3.ReactiveCocoa 中常用的宏 一. 关于宏 宏（Macro），是一种批量处理的称谓。
在编程领域里的宏是一种抽象（Abstraction），它根据一系列预定义的规则替换一定的文本模式。解释器或编译器在遇到宏时会自动进行这一模式替换。绝大多数情况下，“宏”这个词的使用暗示着将小命令或动作转化为一系列指令。
宏的用途在于自动化频繁使用的序列或者是获得一种更强大的抽象能力。 计算机语言如C语言或汇编语言有简单的宏系统，由编译器或汇编器的预处理器实现。C语言的宏预处理器的工作只是简单的文本搜索和替换，使用附加的文本处理语言如M4，C程序员可以获得更精巧的宏。
Lisp类语言如Common Lisp和Scheme有更精巧的宏系统：宏的行为如同是函数对自身程序文本的变形，并且可以应用全部语言来表达这种变形。一个C宏可以定义一段语法的替换，然而一个Lisp的宏却可以控制一节代码的计算。
对于编译语言来说，所有的宏都是在预编译的时候被展开的，所以在lex进行词法扫描生成Token，词法分析过程之前，所有的宏都已经被展开完成了。
对于Xcode，预处理或者预编译阶段是可以直接查看的。
随便写一个宏，然后打开Xcode右上方的Assistant，选择“Preprocess”就可以看到该文件预处理之后的样子了。可以看到左边的@weakify(self) 被转换成了右边的两行代码了。
关于这个Xcode的这个功能还有2点补充说明：
1.不同阶段的Preprocessed可能不同，要根据你的目标去选择预处理的条件。
比如这里就有5种预编译的种类可以选择。
2.宏经过预编译之后出来的代码，是可以用来检测宏写的是否正确的，但是无法看到宏被展开的具体过程。这意味着我们可以通过Xcode这个功能来查看宏的作用，但是无法知道宏的具体实现。具体实现还是需要通过查看源码来分析。
ReactiveCocoa中的宏，如果不查看源码分析，会觉得那些宏都像魔法一样奇妙无比，接下来就来解开“宏”魔法的神秘面纱。
二. ReactiveCocoa 中的元宏 在ReactiveCocoa的宏中，作者定义了这么一些基础的宏，作为“元宏”，它们是构成之后复杂宏的基础。在分析常用宏之前，必须要先分析清楚这些元宏的具体实现。
1. metamacro_stringify(VALUE) #define metamacro_stringify(VALUE) \ metamacro_stringify_(VALUE) #define metamacro_stringify_(VALUE) # VALUE metamacro_stringify( )这个宏用到了#的用法。#在宏中代表把宏的参数变为一个字符串。这个宏的目的和它的名字一样明显，把入参VALUE转换成一个字符串返回。
这里可能就有人有疑问，为啥要包装一层，不能直接写成下面这样：
#define metamacro_stringify(VALUE) # VALUE 语意确实也没有变，但是有种特殊情况下就会出现问题。
举个例子：
#define NUMBER 10 #define ADD(a,b) (a+b) NSLog(@&amp;#34;%d+%d=%d&amp;#34;,NUMBER, NUMBER, ADD(NUMBER,NUMBER)); 输出如下：
10+10=20这样子确实是没有问题，但是稍作修改就会有问题。</description></item><item><title>ReactiveCocoa 中 RACCommand 底层实现分析</title><link>https://new.halfrost.com/zh/reactivecocoa_raccommand/</link><pubDate>Sat, 07 Jan 2017 01:59:00 +0000</pubDate><guid>https://new.halfrost.com/zh/reactivecocoa_raccommand/</guid><description>前言 在ReactiveCocoa 过程中，除去RACSignal和RACSubject这些信号类以外，有些时候我们可能还需要封装一些固定的操作集合。这些操作集合都是固定的，每次只要一触发就会执行事先定义好的一个过程。在iOS开发过程中，按钮的点击事件就可能有这种需求。那么RACCommand就可以实现这种需求。
当然除了封装一个操作集合以外，RACCommand还能集中处理错误等等功能。今天就来从底层来看看RACCommand是如何实现的。
目录 1.RACCommand的定义 2.initWithEnabled: signalBlock: 底层实现分析 3.execute:底层实现分析 4.RACCommand的一些Category 一. RACCommand的定义 首先说说RACCommand的作用。 RACCommand 在ReactiveCocoa 中是对一个动作的触发条件以及它产生的触发事件的封装。
触发条件：初始化RACCommand的入参enabledSignal就决定了RACCommand是否能开始执行。入参enabledSignal就是触发条件。举个例子，一个按钮是否能点击，是否能触发点击事情，就由入参enabledSignal决定。
触发事件：初始化RACCommand的另外一个入参(RACSignal * (^)(id input))signalBlock就是对触发事件的封装。RACCommand每次执行都会调用一次signalBlock闭包。
RACCommand最常见的例子就是在注册登录的时候，点击获取验证码的按钮，这个按钮的点击事件和触发条件就可以用RACCommand来封装，触发条件是一个信号，它可以是验证手机号，验证邮箱，验证身份证等一些验证条件产生的enabledSignal。触发事件就是按钮点击之后执行的事件，可以是发送验证码的网络请求。
RACCommand在ReactiveCocoa中算是很特别的一种存在，因为它的实现并不是FRP实现的，是OOP实现的。RACCommand的本质就是一个对象，在这个对象里面封装了4个信号。
关于RACCommand的定义如下：
@interface RACCommand : NSObject @property (nonatomic, strong, readonly) RACSignal *executionSignals; @property (nonatomic, strong, readonly) RACSignal *executing; @property (nonatomic, strong, readonly) RACSignal *enabled; @property (nonatomic, strong, readonly) RACSignal *errors; @property (atomic, assign) BOOL allowsConcurrentExecution; volatile uint32_t _allowsConcurrentExecution; @property (atomic, copy, readonly) NSArray *activeExecutionSignals; NSMutableArray *_activeExecutionSignals; @property (nonatomic, strong, readonly) RACSignal *immediateEnabled; @property (nonatomic, copy, readonly) RACSignal * (^signalBlock)(id input); @end RACCommand中4个最重要的信号就是定义开头的那4个信号，executionSignals，executing，enabled，errors。需要注意的是，这4个信号基本都是（并不是完全是）在主线程上执行的。</description></item><item><title>ReactiveCocoa 中 RACScheduler 是如何封装 GCD 的</title><link>https://new.halfrost.com/zh/reactivecocoa_racscheduler/</link><pubDate>Sat, 31 Dec 2016 01:09:00 +0000</pubDate><guid>https://new.halfrost.com/zh/reactivecocoa_racscheduler/</guid><description>前言 在使用ReactiveCocoa 过程中，Josh Abernathy和Justin Spahr-Summers 两位大神为了能让RAC的使用者更畅快的在沉浸在FRP的世界里，更好的进行并发编程，于是就对GCD进行了一次封装，并与RAC的各大组件进行了完美的整合。
自从有了RACScheduler以后，使整个RAC并发编程的代码里面更加和谐统一，更加顺手，更加“ReactiveCocoa”。
目录 1.RACScheduler是如何封装GCD的 2.RACScheduler的一些子类 3.RACScheduler是如何“取消”并发任务的 4.RACScheduler是如何和RAC其他组件进行完美整合的 一. RACScheduler是如何封装GCD的 RACScheduler在ReactiveCocoa中到底是干嘛的呢？处于什么地位呢？官方给出的定义如下：
Schedulers are used to control when and where work is performedRACScheduler在ReactiveCocoa中是用来控制一个任务，何时何地被执行。它主要是用来解决ReactiveCocoa中并发编程的问题的。
RACScheduler的实质是对GCD的封装，底层就是GCD实现的。
要分析RACScheduler，先来回顾一下GCD。
众所周知，在GCD中，Dispatch Queue主要分为2类，Serial Dispatch Queue 和 Concurrent Dispatch Queue 。其中Serial Dispatch Queue是等待现在执行中处理结束的队列，Concurrent Dispatch Queue是不等待现在执行中处理结束的队列。
生成Dispatch Queue的方法也有2种，第一种方式是通过GCD的API生成Dispatch Queue。
生成Serial Dispatch Queue
dispatch_queue_t serialDispatchQueue = dispatch_queue_create(&amp;#34;com.gcd.SerialDispatchQueue&amp;#34;, DISPATCH_QUEUE_SERIAL); 生成Concurrent Dispatch Queue
dispatch_queue_t concurrentDispatchQueue = dispatch_queue_create(&amp;#34;com.gcd.ConcurrentDispatchQueue&amp;#34;, DISPATCH_QUEUE_CONCURRENT); 第二种方法是直接获取系统提供的Dispatch Queue。系统提供的也分为2类，Main Dispatch Queue 和 Global Dispatch Queue。Main Dispatch Queue 对应着是Serial Dispatch Queue，Global Dispatch Queue 对应着是Concurrent Dispatch Queue。</description></item><item><title>ReactiveCocoa 中 集合类 RACSequence 和 RACTuple 底层实现分析</title><link>https://new.halfrost.com/zh/reactivecocoa_racsequence_ractuple/</link><pubDate>Sun, 25 Dec 2016 07:54:00 +0000</pubDate><guid>https://new.halfrost.com/zh/reactivecocoa_racsequence_ractuple/</guid><description>前言 在OOP的世界里使用FRP的思想来编程，光有函数这种一等公民，还是无法满足我们一些需求的。因此还是需要引用变量来完成各式各样的类的操作行为。
在前几篇文章中详细的分析了RACStream中RACSignal的底层实现。RACStream还有另外一个子类，RACSequence，这个类是RAC专门为集合而设计的。这篇文章就专门分析一下RACSequence的底层实现。
目录 1.RACTuple底层实现分析 2.RACSequence底层实现分析 3.RACSequence操作实现分析 4.RACSequence的一些扩展 一. RACTuple底层实现分析 在分析RACSequence之前，先来看看RACTuple的实现。RACTuple是ReactiveCocoa的元组类。
1. RACTuple @interface RACTuple : NSObject &amp;lt;NSCoding, NSCopying, NSFastEnumeration&amp;gt; @property (nonatomic, readonly) NSUInteger count; @property (nonatomic, readonly) id first; @property (nonatomic, readonly) id second; @property (nonatomic, readonly) id third; @property (nonatomic, readonly) id fourth; @property (nonatomic, readonly) id fifth; @property (nonatomic, readonly) id last; @property (nonatomic, strong) NSArray *backingArray; @property (nonatomic, copy, readonly) RACSequence *rac_sequence; // 这个是专门为sequence提供的一个扩展 @end RACTuple的定义看上去很简单，底层实质就是一个NSArray，只不过封装了一些方法。RACTuple继承了NSCoding, NSCopying, NSFastEnumeration这三个协议。</description></item><item><title>ReactiveCocoa 中 RACSignal 所有变换操作底层实现分析(下)</title><link>https://new.halfrost.com/zh/reactivecocoa_racsignal_operations3/</link><pubDate>Sat, 10 Dec 2016 09:12:00 +0000</pubDate><guid>https://new.halfrost.com/zh/reactivecocoa_racsignal_operations3/</guid><description>前言 紧接着上篇的源码实现分析，继续分析RACSignal的变换操作的底层实现。
目录 1.高阶信号操作 2.同步操作 3.副作用操作 4.多线程操作 5.其他操作 一. 高阶信号操作 高阶操作大部分的操作是针对高阶信号的，也就是说信号里面发送的值还是一个信号或者是一个高阶信号。可以类比数组，这里就是多维数组，数组里面还是套的数组。
1. flattenMap: (在父类RACStream中定义的) flattenMap:在整个RAC中具有很重要的地位，很多信号变换都是可以用flattenMap:来实现的。
map:，flatten，filter，sequenceMany:这4个操作都是用flattenMap:来实现的。然而其他变换操作实现里面用到map:，flatten，filter又有很多。
回顾一下map:的实现：
- (instancetype)map:(id (^)(id value))block { NSCParameterAssert(block != nil); Class class = self.class; return [[self flattenMap:^(id value) { return [class return:block(value)]; }] setNameWithFormat:@&amp;#34;[%@] -map:&amp;#34;, self.name]; } map:的操作其实就是直接原信号进行的 flattenMap:的操作，变换出来的新的信号的值是block(value)。
flatten的实现接下去会具体分析，这里先略过。
filter的实现：
- (instancetype)filter:(BOOL (^)(id value))block { NSCParameterAssert(block != nil); Class class = self.class; return [[self flattenMap:^ id (id value) { block(value) ? return [class return:value] : return class.</description></item><item><title>ReactiveCocoa 中 RACSignal 冷信号和热信号底层实现分析</title><link>https://new.halfrost.com/zh/reactivecocoa_hot_cold_signal/</link><pubDate>Sun, 04 Dec 2016 22:15:00 +0000</pubDate><guid>https://new.halfrost.com/zh/reactivecocoa_hot_cold_signal/</guid><description>前言 关于ReactiveCocoa v2.5中冷信号和热信号的文章中，最著名的就是美团的臧成威老师写的3篇冷热信号的文章：
细说ReactiveCocoa的冷信号与热信号（一）
细说ReactiveCocoa的冷信号与热信号（二）：为什么要区分冷热信号
细说ReactiveCocoa的冷信号与热信号（三）：怎么处理冷信号与热信号
由于最近在写关于RACSignal底层实现分析的文章，当然也逃不了关于冷热信号操作的分析。这篇文章打算分析分析如何从冷信号转成热信号的底层实现。
目录 1.关于冷信号和热信号的概念 2.RACSignal热信号 3.RACSignal冷信号 4.冷信号是如何转换成热信号的 一. 关于冷信号和热信号的概念 冷热信号的概念是源自于源于.NET框架Reactive Extensions(RX)中的Hot Observable和Cold Observable，
Hot Observable是主动的，尽管你并没有订阅事件，但是它会时刻推送，就像鼠标移动；而Cold Observable是被动的，只有当你订阅的时候，它才会发布消息。
Hot Observable可以有多个订阅者，是一对多，集合可以与订阅者共享信息；而Cold Observable只能一对一，当有不同的订阅者，消息是重新完整发送。
在这篇文章细说ReactiveCocoa的冷信号与热信号（一）详细分析了冷热信号的特点：
热信号是主动的，即使你没有订阅事件，它仍然会时刻推送。而冷信号是被动的，只有当你订阅的时候，它才会发送消息。
热信号可以有多个订阅者，是一对多，信号可以与订阅者共享信息。而冷信号只能一对一，当有不同的订阅者，消息会从新完整发送。
二. RACSignal热信号 RACSignal家族中符合热信号的特点的信号有以下几个。
1.RACSubject @interface RACSubject : RACSignal &amp;lt;RACSubscriber&amp;gt; @property (nonatomic, strong, readonly) NSMutableArray *subscribers; @property (nonatomic, strong, readonly) RACCompoundDisposable *disposable; - (void)enumerateSubscribersUsingBlock:(void (^)(id&amp;lt;RACSubscriber&amp;gt; subscriber))block; + (instancetype)subject; @end 首先来看看RACSubject的定义。
RACSubject是继承自RACSignal，并且它还遵守RACSubscriber协议。这就意味着它既能订阅信号，也能发送信号。
在RACSubject里面有一个NSMutableArray数组，里面装着该信号的所有订阅者。其次还有一个RACCompoundDisposable信号，里面装着该信号所有订阅者的RACDisposable。
RACSubject之所以能称之为热信号，那么它肯定是符合上述热信号的定义的。让我们从它的实现来看看它是如何符合的。
- (RACDisposable *)subscribe:(id&amp;lt;RACSubscriber&amp;gt;)subscriber { NSCParameterAssert(subscriber !</description></item><item><title>ReactiveCocoa 中 RACSignal 所有变换操作底层实现分析(中)</title><link>https://new.halfrost.com/zh/reactivecocoa_racsignal_operations2/</link><pubDate>Tue, 29 Nov 2016 20:52:00 +0000</pubDate><guid>https://new.halfrost.com/zh/reactivecocoa_racsignal_operations2/</guid><description>前言 紧接着上篇的源码实现分析，继续分析RACSignal的变换操作的底层实现。
目录 1.过滤操作 2.组合操作 一. 过滤操作 过滤操作也属于一种变换，根据过滤条件，过滤出符合条件的值。变换出来的新的信号是原信号的一个子集。
1. filter: (在父类RACStream中定义的) 这个filter:操作在any:的实现中用到过了。
- (instancetype)filter:(BOOL (^)(id value))block { NSCParameterAssert(block != nil); Class class = self.class; return [[self flattenMap:^ id (id value) { if (block(value)) { return [class return:value]; } else { return class.empty; } }] setNameWithFormat:@&amp;#34;[%@] -filter:&amp;#34;, self.name]; } filter:中传入一个闭包，是用筛选的条件。如果满足筛选条件的即返回原信号的值，否则原信号的值被“吞”掉，返回空的信号。这个变换主要是用flattenMap的。
2. ignoreValues - (RACSignal *)ignoreValues { return [[self filter:^(id _) { return NO; }] setNameWithFormat:@&amp;#34;[%@] -ignoreValues&amp;#34;, self.name]; } 由上面filter的实现，这里把筛选判断条件永远的传入NO，那么原信号的值都会被变换成empty信号，故变换之后的信号为空信号。</description></item><item><title>ReactiveCocoa 中 RACSignal 所有变换操作底层实现分析(上)</title><link>https://new.halfrost.com/zh/reactivecocoa_racsignal_operations1/</link><pubDate>Sat, 26 Nov 2016 00:42:12 +0000</pubDate><guid>https://new.halfrost.com/zh/reactivecocoa_racsignal_operations1/</guid><description>前言 在上篇文章中，详细分析了RACSignal是创建和订阅的详细过程。看到底层源码实现后，就能发现，ReactiveCocoa这个FRP的库，实现响应式（RP）是用Block闭包来实现的，而并不是用KVC / KVO实现的。
在ReactiveCocoa整个库中，RACSignal占据着比较重要的位置，而RACSignal的变换操作更是整个RACStream流操作核心之一。在上篇文章中也详细分析了bind操作的实现。RACsignal很多变换操作都是基于bind操作来实现的。在开始本篇底层实现分析之前，先简单回顾一下上篇文章中分析的bind函数，这是这篇文章分析的基础。
bind函数可以简单的缩写成下面这样子。
- (RACSignal *)bind:(RACStreamBindBlock (^)(void))block; { return [RACSignal createSignal:^RACDisposable *(id&amp;lt;RACSubscriber&amp;gt; subscriber) { RACStreamBindBlock bindBlock = block(); [self subscribeNext:^(id x) { //(1)  BOOL stop = NO; RACSignal *signal = (RACSignal *)bindBlock(x, &amp;amp;stop); //(2)  if (signal == nil || stop) { [subscriber sendCompleted]; } else { [signal subscribeNext:^(id x) { [subscriber sendNext:x]; //(3)  } error:^(NSError *error) { [subscriber sendError:error]; } completed:^{ }]; } } error:^(NSError *error) { [subscriber sendError:error]; } completed:^{ [subscriber sendCompleted]; }]; return nil; }]; } 当bind变换之后的信号被订阅，就开始执行bind函数中return的block闭包。</description></item><item><title>ReactiveCocoa 中 RACSignal 是如何发送信号的</title><link>https://new.halfrost.com/zh/reactivecocoa_racsignal/</link><pubDate>Mon, 14 Nov 2016 09:48:43 +0000</pubDate><guid>https://new.halfrost.com/zh/reactivecocoa_racsignal/</guid><description>前言 ReactiveCocoa是一个(第一个？)将函数响应式编程范例带入Objective-C的开源库。ReactiveCocoa是由Josh Abernathy和Justin Spahr-Summers 两位大神在对GitHub for Mac的开发过程中编写的。Justin Spahr-Summers 大神在2011年11月13号下午12点35分进行的第一次提交，直到2013年2月13日上午3点05分发布了其1.0 release，达到了第一个重要里程碑。ReactiveCocoa社区也非常活跃，目前最新版已经完成了ReactiveCocoa 5.0.0-alpha.3，目前在5.0.0-alpha.4开发中。
ReactiveCocoa v2.5 是公认的Objective-C最稳定的版本，因此被广大的以OC为主要语言的客户端选中使用。ReactiveCocoa v3.x主要是基于Swift 1.2的版本，而ReactiveCocoa v4.x 主要基于Swift 2.x，ReactiveCocoa 5.0就全面支持Swift 3.0，也许还有以后的Swift 4.0。接下来几篇博客先以ReactiveCocoa v2.5版本为例子，分析一下OC版的RAC具体实现（也许分析完了RAC 5.0就到来了）。也算是写在ReactiveCocoa 5.0正式版到来前夕的祝福吧。
目录 1.什么是ReactiveCocoa？ 2.RAC中的核心RACSignal发送与订阅流程 3.RACSignal操作的核心bind实现 4.RACSignal基本操作concat和zipWith实现 5.最后 一. 什么是ReactiveCocoa？ ReactiveCocoa（其简称为RAC）是由Github 开源的一个应用于iOS和OS X开发的新框架。RAC具有函数式编程(FP)和响应式编程(RP)的特性。它主要吸取了.Net的 Reactive Extensions的设计和实现。
ReactiveCocoa 的宗旨是Streams of values over time ，随着时间变化而不断流动的数据流。
ReactiveCocoa 主要解决了以下这些问题：
UI数据绑定 UI控件通常需要绑定一个事件，RAC可以很方便的绑定任何数据流到控件上。
用户交互事件绑定 RAC为可交互的UI控件提供了一系列能发送Signal信号的方法。这些数据流会在用户交互中相互传递。
解决状态以及状态之间依赖过多的问题 有了RAC的绑定之后，可以不用在关心各种复杂的状态，isSelect，isFinish……也解决了这些状态在后期很难维护的问题。
消息传递机制的大统一 OC中编程原来消息传递机制有以下几种：Delegate，Block Callback，Target-Action，Timers，KVO，objc上有一篇关于OC中这5种消息传递方式改如何选择的文章Communication Patterns，推荐大家阅读。现在有了RAC之后，以上这5种方式都可以统一用RAC来处理。
二. RAC中的核心RACSignal ReactiveCocoa 中最核心的概念之一就是信号RACStream。RACStream中有两个子类——RACSignal 和 RACSequence。本文先来分析RACSignal。</description></item><item><title>Objc 对象的今生今世</title><link>https://new.halfrost.com/zh/objc_life/</link><pubDate>Sun, 30 Oct 2016 17:11:38 +0000</pubDate><guid>https://new.halfrost.com/zh/objc_life/</guid><description>前言 在面向对象编程中，我们每天都在创建对象，用对象描述着整个世界，然而对象是如何从孕育到销毁的呢？
目录 1.孕育对象 2.对象的出生 3.对象的成长 4.对象的销毁 5.总结 一.孕育对象 每天开发我们都在alloc对象，而alloc方法做了些什么呢？
+ (id)alloc { return _objc_rootAlloc(self); } 所有对象alloc都会调用这个root的方法
id _objc_rootAlloc(Class cls) { return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/); } 这个方法又会去调用callAlloc方法
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) { if (checkNil &amp;amp;&amp;amp; !cls) return nil; #if __OBJC2__  if (! cls-&amp;gt;ISA()-&amp;gt;hasCustomAWZ()) { // No alloc/allocWithZone implementation. Go straight to the allocator.  // fixme store hasCustomAWZ in the non-meta class and  // add it to canAllocFast&amp;#39;s summary  if (cls-&amp;gt;canAllocFast()) { // No ctors, raw isa, etc.</description></item><item><title>Realm 数据库 从入门到“放弃”</title><link>https://new.halfrost.com/zh/realm_ios/</link><pubDate>Sat, 22 Oct 2016 10:12:00 +0000</pubDate><guid>https://new.halfrost.com/zh/realm_ios/</guid><description>前言 由于最近项目中在用Realm，所以把自己实践过程中的一些心得总结分享一下。
Realm是由Y Combinator公司孵化出来的一款可以用于iOS(同样适用于Swift&amp;amp;Objective-C)和Android的跨平台移动数据库。目前最新版是Realm 2.0.2，支持的平台包括Java，Objective-C，Swift，React Native，Xamarin。
Realm官网上说了好多优点，我觉得选用Realm的最吸引人的优点就三点：
跨平台：现在很多应用都是要兼顾iOS和Android两个平台同时开发。如果两个平台都能使用相同的数据库，那就不用考虑内部数据的架构不同，使用Realm提供的API，可以使数据持久化层在两个平台上无差异化的转换。
简单易用：Core Data 和 SQLite 冗余、繁杂的知识和代码足以吓退绝大多数刚入门的开发者，而换用 Realm，则可以极大地学习成本，立即学会本地化存储的方法。毫不吹嘘的说，把官方最新文档完整看一遍，就完全可以上手开发了。
可视化：Realm 还提供了一个轻量级的数据库查看工具，在Mac Appstore 可以下载“Realm Browser”这个工具，开发者可以查看数据库当中的内容，执行简单的插入和删除数据的操作。毕竟，很多时候，开发者使用数据库的理由是因为要提供一些所谓的“知识库”。
“Realm Browser”这个工具调试起Realm数据库实在太好用了，强烈推荐。
如果使用模拟器进行调试,可以通过
[RLMRealmConfiguration defaultConfiguration].fileURL 打印出Realm 数据库地址,然后在Finder中⌘⇧G跳转到对应路径下,用Realm Browser打开对应的.realm文件就可以看到数据啦.
如果是使用真机调试的话“Xcode-&amp;gt;Window-&amp;gt;Devices(⌘⇧2 )”,然后找到对应的设备与项目,点击Download Container，导出xcappdata文件后,显示包内容,进到AppData-&amp;gt;Documents,使用Realm Browser打开.realm文件即可.
自2012年起， Realm 就已经开始被用于正式的商业产品中了。经过4年的使用，逐步趋于稳定。
目录 1.Realm 安装 2.Realm 中的相关术语 3.Realm 入门——如何使用 4.Realm 使用中可能需要注意的一些问题 5.Realm “放弃”——优点和缺点 6.Realm 到底是什么？ 7.总结 一. Realm 安装 使用 Realm 构建应用的基本要求：
iOS 7 及其以上版本, macOS 10.9 及其以上版本，此外 Realm 支持 tvOS 和 watchOS 的所有版本。 需要使用 Xcode 7.</description></item><item><title>iOS 如何实现 Aspect Oriented Programming</title><link>https://new.halfrost.com/zh/ios_aspect/</link><pubDate>Sat, 15 Oct 2016 09:54:00 +0000</pubDate><guid>https://new.halfrost.com/zh/ios_aspect/</guid><description>前言 在“Runtime病院”住院的后两天，分析了一下AOP的实现原理。“出院”后，发现Aspect库还没有详细分析，于是就有了这篇文章，今天就来说说iOS 是如何实现Aspect Oriented Programming。
目录 1.Aspect Oriented Programming简介 2.什么是Aspects 3.Aspects 中4个基本类 解析 4.Aspects hook前的准备工作 5.Aspects hook过程详解 6.关于Aspects的一些 “坑” 一.Aspect Oriented Programming简介 面向切面的程序设计（aspect-oriented programming，AOP，又译作面向方面的程序设计、观点导向编程、剖面导向程序设计）是计算机科学中的一个术语，指一种程序设计范型。该范型以一种称为侧面（aspect，又译作方面）的语言构造为基础，侧面是一种新的模块化机制，用来描述分散在对象、类或函数中的横切关注点（crosscutting concern）。
侧面的概念源于对面向对象的程序设计的改进，但并不只限于此，它还可以用来改进传统的函数。与侧面相关的编程概念还包括元对象协议、主题（subject）、混入（mixin）和委托。
AOP通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
OOP（面向对象编程）针对业务处理过程的实体及其属性和行为进行抽象封装，以获得更加清晰高效的逻辑单元划分。
AOP则是针对业务处理过程中的切面进行提取，它所面对的是处理过程中的某个步骤或阶段，以获得逻辑过程中各部分之间低耦合性的隔离效果。
OOP和AOP属于两个不同的“思考方式”。OOP专注于对象的属性和行为的封装，AOP专注于处理某个步骤和阶段的，从中进行切面的提取。
举个例子，如果有一个判断权限的需求，OOP的做法肯定是在每个操作前都加入权限判断。那日志记录怎么办？在每个方法的开始结束的地方都加上日志记录。AOP就是把这些重复的逻辑和操作，提取出来，运用动态代理，实现这些模块的解耦。OOP和AOP不是互斥，而是相互配合。
在iOS里面使用AOP进行编程，可以实现非侵入。不需要更改之前的代码逻辑，就能加入新的功能。主要用来处理一些具有横切性质的系统性服务，如日志记录、权限管理、缓存、对象池管理等。
二. 什么是Aspects Aspects是一个轻量级的面向切面编程的库。它能允许你在每一个类和每一个实例中存在的方法里面加入任何代码。可以在以下切入点插入代码：before(在原始的方法前执行) / instead(替换原始的方法执行) / after(在原始的方法后执行,默认)。通过Runtime消息转发实现Hook。Aspects会自动的调用super方法，使用method swizzling起来会更加方便。
这个库很稳定，目前用在数百款app上了。它也是PSPDFKit的一部分，PSPDFKit是一个iOS 看PDF的framework库。作者最终决定把它开源出来。
三.Aspects 中4个基本类 解析 我们从头文件开始看起。
1.Aspects.h typedef NS_OPTIONS(NSUInteger, AspectOptions) { AspectPositionAfter = 0, /// Called after the original implementation (default)  AspectPositionInstead = 1, /// Will replace the original implementation.</description></item><item><title>Ghost 博客搭建日记</title><link>https://new.halfrost.com/zh/ghost_build/</link><pubDate>Sun, 02 Oct 2016 22:13:00 +0000</pubDate><guid>https://new.halfrost.com/zh/ghost_build/</guid><description>前言 今年7月阴错阳差的给自己买了一台阿里云服务器，当时是想着自己折腾折腾后台，看能否打通前端和后端之间的任督二脉。直到我发现我原来放在GitPage上的博客访问速度慢的实在不能忍，痛下决心之后，就把原来Jekyll搭建的博客站点一口气都迁移到了现在自己阿里云的主机上了。原来的Jekyll博客还在，还在GitPage上。换到了国内自己的阿里云主机上，我就用了优雅的Ghost搭建我的新家了。
今年8月的时候，博客上线了，网友们看见我这个博客还不错，让我出一下搭建教程。虽然博客上线，还没有通过网络管理中心的审核，还不知道之后会发生什么。当时的我也对Ghost不是很熟，也不知道后期如何维护，所以想着先让Ghost在服务器上面跑一个月看看，有了心得体会之后在写篇文章记录一下搭建过程。
现在博客也跑了一个多月了，日常维护都玩的转了，于是就有了这一篇文章了。
目录 1.Ghost 简介 2.搭建前准备清单 3.开始搭建 4.全站 Https 5.管局备案 / 公安备案 6.CDN 优化访问速度 7.后期维护 一.Ghost简介 Ghost 是一套基于 Node.js 构建的开源博客平台（Open source blogging platform），具有易用的书写界面和体验，博客内容默认采用 Markdown 语法书写，不过原生的不支持Markdown的表格和LaTeX，如果需要使用，需要在服务器端安装插件。
Ghost目标是取代臃肿的 Wordpress，界面简洁，专注写作，支持在线预览和在线写作。
Ghost属于动态博客，页面并不是像Hexo,Jekyll这类静态博客，在编译的时候会生成所有页面。Ghost有前台和后台。后台负责写作，发布文章，系统配置，等等。
1. Ghost的优势和劣势 这里有篇文章是这样评论Ghost的优缺点的
优势 技术上,采用NodeJs，在可预见的未来里，无疑比PHP有更多优势，并发能力远超Wordpress，虽然NodeJs后期维护成本高，但是我们只是借它做博客而已。 易用性上，专注写作，评论，超炫皮肤，完美支持 MarkDown,没有Wordpress那么臃肿，回归到博客最原始的状态,传递文字最原始的力量。 使用上，便捷，随时随地编辑，比Hexo,Jekyll这类静态博客要书写方便，特别是在不同电脑上写作时。
劣势 需要配套支持Node环境的虚拟机，一般免费的很少支持，这时必须得掏腰包了。 后台简陋,许多功能还未完善，不过写作这一块没啥大问题。
关于劣势，我再说一点，Ghost没有Hexo上面那么丰富的插件。
2. Ghost的亮点： 采用Mysql作为数据库,通用快速上手，这里也可以用其他数据库比如Sqlite。 Nginx作为反向代理,配置多个Ghost博客,同时也能增加了网站的负载。 非常简易化的Ubuntu的Node.js安装方法，不用编译打包。 安装系统服务,开机重启Ghost服务，免去日后以后操作。 采用Font Awesome作为社交按钮，也可以自定义图标。 highlight.js 作为主题的代码高亮引擎 整合Disqus评论系统,建立属于自己的Discuss圈 国外优秀免费Ghost主题资源分享 整合百度统计以及百度分享 二. 搭建前准备清单 一个可用的域名 一台服务器 ( 我买的阿里云ECS ，服务器系统安装的是 CentOS 7.</description></item><item><title>神经病院 Objective-C Runtime 出院第三天——如何正确使用 Runtime</title><link>https://new.halfrost.com/zh/how_to_use_runtime/</link><pubDate>Fri, 30 Sep 2016 05:00:00 +0000</pubDate><guid>https://new.halfrost.com/zh/how_to_use_runtime/</guid><description>前言 到了今天终于要&amp;quot;出院&amp;quot;了，要总结一下住院几天的收获，谈谈Runtime到底能为我们开发带来些什么好处。当然它也是把双刃剑，使用不当的话，也会成为开发路上的一个大坑。
####目录
1.Runtime的优点 (1) 实现多继承Multiple Inheritance (2) Method Swizzling (3) Aspect Oriented Programming (4) Isa Swizzling (5) Associated Object关联对象 (6) 动态的增加方法 (7) NSCoding的自动归档和自动解档 (8) 字典和模型互相转换 2.Runtime的缺点 一. 实现多继承Multiple Inheritance 在上一篇文章里面讲到的forwardingTargetForSelector:方法就能知道，一个类可以做到继承多个类的效果，只需要在这一步将消息转发给正确的类对象就可以模拟多继承的效果。
在官方文档上记录了这样一段例子。
在OC程序中可以借用消息转发机制来实现多继承的功能。 在上图中，一个对象对一个消息做出回应，类似于另一个对象中的方法借过来或是“继承”过来一样。 在图中，warrior实例转发了一个negotiate消息到Diplomat实例中，执行Diplomat中的negotiate方法，结果看起来像是warrior实例执行了一个和Diplomat实例一样的negotiate方法，其实执行者还是Diplomat实例。
这使得不同继承体系分支下的两个类可以“继承”对方的方法，这样一个类可以响应自己继承分支里面的方法，同时也能响应其他不相干类发过来的消息。在上图中Warrior和Diplomat没有继承关系，但是Warrior将negotiate消息转发给了Diplomat后，就好似Diplomat是Warrior的超类一样。
消息转发提供了许多类似于多继承的特性，但是他们之间有一个很大的不同：
多继承：合并了不同的行为特征在一个单独的对象中，会得到一个重量级多层面的对象。
消息转发：将各个功能分散到不同的对象中，得到的一些轻量级的对象，这些对象通过消息通过消息转发联合起来。
这里值得说明的一点是，即使我们利用转发消息来实现了“假”继承，但是NSObject类还是会将两者区分开。像respondsToSelector:和 isKindOfClass:这类方法只会考虑继承体系，不会考虑转发链。比如上图中一个Warrior对象如果被问到是否能响应negotiate消息：
if ( [aWarrior respondsToSelector:@selector(negotiate)] ) 结果是NO，虽然它能够响应negotiate消息而不报错，但是它是靠转发消息给Diplomat类来响应消息的。
如果非要制造假象，反应出这种“假”的继承关系，那么需要重新实现 respondsToSelector:和 isKindOfClass:来加入你的转发算法：
- (BOOL)respondsToSelector:(SEL)aSelector { if ( [super respondsToSelector:aSelector] ) return YES; else { /* Here, test whether the aSelector message can * * be forwarded to another object and whether that * * object can respond to it.</description></item><item><title>神经病院 Objective-C Runtime 住院第二天——消息发送与转发</title><link>https://new.halfrost.com/zh/objc_runtime_objc_msgsend/</link><pubDate>Sat, 17 Sep 2016 01:47:58 +0000</pubDate><guid>https://new.halfrost.com/zh/objc_runtime_objc_msgsend/</guid><description>前言 现在越来越多的app都使用了JSPatch实现app热修复，而JSPatch 能做到通过 JS 调用和改写 OC 方法最根本的原因是 Objective-C 是动态语言，OC 上所有方法的调用/类的生成都通过 Objective-C Runtime 在运行时进行，我们可以通过类名/方法名反射得到相应的类和方法，也可以替换某个类的方法为新的实现，理论上你可以在运行时通过类名/方法名调用到任何 OC 方法，替换任何类的实现以及新增任意类。今天就来详细解析一下OC中runtime最为吸引人的地方。
####目录
1.objc_msgSend函数简介 2.消息发送Messaging阶段—objc_msgSend源码解析 3.消息转发Message Forwarding阶段 4.forwardInvocation的例子 5.入院考试 6.Runtime中的优化 一.objc_msgSend函数简介 最初接触到OC Runtime，一定是从[receiver message]这里开始的。[receiver message]会被编译器转化为：
id objc_msgSend ( id self, SEL op, ... ); 这是一个可变参数函数。第二个参数类型是SEL。SEL在OC中是selector方法选择器。
typedef struct objc_selector *SEL; objc_selector是一个映射到方法的C字符串。需要注意的是@selector()选择子只与函数名有关。不同类中相同名字的方法所对应的方法选择器是相同的，即使方法名字相同而变量类型不同也会导致它们具有相同的方法选择器。由于这点特性，也导致了OC不支持函数重载。
在receiver拿到对应的selector之后，如果自己无法执行这个方法，那么该条消息要被转发。或者临时动态的添加方法实现。如果转发到最后依旧没法处理，程序就会崩溃。
所以编译期仅仅是确定了要发送消息，而消息如何处理是要运行期需要解决的事情。
objc_msgSend函数究竟会干什么事情呢？从这篇「objc_msgSend() Tour」文章里面可以得到一个比较详细的结论。
1. Check for ignored selectors (GC) and short-circuit. 2. Check for nil target. If nil &amp;amp; nil receiver handler configured, jump to handler If nil &amp;amp; no handler (default), cleanup and return.</description></item><item><title>神经病院 Objective-C Runtime 入院第一天—— isa 和 Class</title><link>https://new.halfrost.com/zh/objc_runtime_isa_class/</link><pubDate>Sat, 10 Sep 2016 01:25:00 +0000</pubDate><guid>https://new.halfrost.com/zh/objc_runtime_isa_class/</guid><description>前言 我第一次开始重视Objective-C Runtime是从2014年11月1日，@唐巧老师在微博上发的一条微博开始。
这是sunnyxx在线下的一次分享会。会上还给了4道题目。
这4道题以我当时的知识，很多就不确定，拿不准。从这次入院考试开始，就成功入院了。后来这两年对Runtime的理解慢慢增加了，打算今天自己总结总结平时一直躺在我印象笔记里面的笔记。有些人可能有疑惑，学习Runtime到底有啥用，平时好像并不会用到。希望看完我这次的总结，心中能解开一些疑惑。
####目录
1.Runtime简介 2.NSObject起源 (1) isa_t结构体的具体实现 (2) cache_t的具体实现 (3) class_data_bits_t的具体实现 3.入院考试 一. Runtime简介 Runtime 又叫运行时，是一套底层的 C 语言 API，是 iOS 系统的核心之一。开发者在编码过程中，可以给任意一个对象发送消息，在编译阶段只是确定了要向接收者发送这条消息，而接受者将要如何响应和处理这条消息，那就要看运行时来决定了。
C语言中，在编译期，函数的调用就会决定调用哪个函数。 而OC的函数，属于动态调用过程，在编译期并不能决定真正调用哪个函数，只有在真正运行时才会根据函数的名称找到对应的函数来调用。
Objective-C 是一个动态语言，这意味着它不仅需要一个编译器，也需要一个运行时系统来动态得创建类和对象、进行消息传递和转发。
Objc 在三种层面上与 Runtime 系统进行交互：
1. 通过 Objective-C 源代码 一般情况开发者只需要编写 OC 代码即可，Runtime 系统自动在幕后把我们写的源代码在编译阶段转换成运行时代码，在运行时确定对应的数据结构和调用具体哪个方法。
2. 通过 Foundation 框架的 NSObject 类定义的方法 在OC的世界中，除了NSProxy类以外，所有的类都是NSObject的子类。在Foundation框架下，NSObject和NSProxy两个基类，定义了类层次结构中该类下方所有类的公共接口和行为。NSProxy是专门用于实现代理对象的类，这个类暂时本篇文章不提。这两个类都遵循了NSObject协议。在NSObject协议中，声明了所有OC对象的公共方法。
在NSObject协议中，有以下5个方法，是可以从Runtime中获取信息，让对象进行自我检查。
- (Class)class OBJC_SWIFT_UNAVAILABLE(&amp;#34;use &amp;#39;anObject.dynamicType&amp;#39; instead&amp;#34;); - (BOOL)isKindOfClass:(Class)aClass; - (BOOL)isMemberOfClass:(Class)aClass; - (BOOL)conformsToProtocol:(Protocol *)aProtocol; - (BOOL)respondsToSelector:(SEL)aSelector; -class方法返回对象的类； -isKindOfClass: 和 -isMemberOfClass: 方法检查对象是否存在于指定的类的继承体系中； -respondsToSelector: 检查对象能否响应指定的消息； -conformsToProtocol:检查对象是否实现了指定协议类的方法；</description></item><item><title>深入研究 Block 用 weakSelf、strongSelf、@weakify、@strongify 解决循环引用</title><link>https://new.halfrost.com/zh/ios_block_retain_circle/</link><pubDate>Sun, 04 Sep 2016 11:38:04 +0000</pubDate><guid>https://new.halfrost.com/zh/ios_block_retain_circle/</guid><description>前言 在上篇中，仔细分析了一下Block的实现原理以及__block捕获外部变量的原理。然而实际使用Block过程中，还是会遇到一些问题，比如Retain Circle的问题。
####目录
1.Retain Circle的由来 2.__weak、__strong的实现原理 3.weakSelf、strongSelf的用途 4.@weakify、@strongify实现原理 一. Retain Circle的由来 循环引用的问题相信大家都很理解了，这里还是简单的提一下。
当A对象里面强引用了B对象，B对象又强引用了A对象，这样两者的retainCount值一直都无法为0，于是内存始终无法释放，导致内存泄露。所谓的内存泄露就是本应该释放的对象，在其生命周期结束之后依旧存在。
这是2个对象之间的，相应的，这种循环还能存在于3，4……个对象之间，只要相互形成环，就会导致Retain Cicle的问题。
当然也存在自身引用自身的，当一个对象内部的一个obj，强引用的自身，也会导致循环引用的问题出现。常见的就是block里面引用的问题。
二.__weak、__strong的实现原理 在ARC环境下，id类型和对象类型和C语言其他类型不同，类型前必须加上所有权的修饰符。
所有权修饰符总共有4种：
1.__strong修饰符 2.__weak修饰符 3.__unsafe_unretained修饰符 4.__autoreleasing修饰符
一般我们如果不写，默认的修饰符是__strong。
要想弄清楚__strong，__weak的实现原理，我们就需要研究研究clang(LLVM编译器)和objc4 Objective-C runtime库了。
关于clang有一份关于ARC详细的文档，有兴趣的可以仔细研究一下文档里面的说明和例子，很有帮助。
以下的讲解，也会来自于上述文档中的函数说明。
1.__strong的实现原理 (1)对象持有自己 首先我们先来看看生成的对象持有自己的情况，利用alloc/new/copy/mutableCopy生成对象。
当我们声明了一个__strong对象
{ id __strong obj = [[NSObject alloc] init]; } LLVM编译器会把上述代码转换成下面的样子
id __attribute__((objc_ownership(strong))) obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass(&amp;#34;NSObject&amp;#34;), sel_registerName(&amp;#34;alloc&amp;#34;)), sel_registerName(&amp;#34;init&amp;#34;)); 相应的会调用
id obj = objc_msgSend(NSObject, @selector(alloc)); objc_msgSend(obj,selector(init)); objc_release(obj); 上述这些方法都好理解。在ARC有效的时候就会自动插入release代码，在作用域结束的时候自动释放。
(2)对象不持有自己 生成对象的时候不用alloc/new/copy/mutableCopy等方法。</description></item><item><title>深入研究 Block 捕获外部变量和 __block 实现原理</title><link>https://new.halfrost.com/zh/ios_block/</link><pubDate>Sat, 27 Aug 2016 06:54:28 +0000</pubDate><guid>https://new.halfrost.com/zh/ios_block/</guid><description>前言 Blocks是C语言的扩充功能，而Apple 在OS X Snow Leopard 和 iOS 4中引入了这个新功能“Blocks”。从那开始，Block就出现在iOS和Mac系统各个API中，并被大家广泛使用。一句话来形容Blocks，带有自动变量（局部变量）的匿名函数。
Block在OC中的实现如下：
struct Block_layout { void *isa; int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor *descriptor; /* Imported variables. */ }; struct Block_descriptor { unsigned long int reserved; unsigned long int size; void (*copy)(void *dst, void *src); void (*dispose)(void *); }; 从结构图中很容易看到isa，所以OC处理Block是按照对象来处理的。在iOS中，isa常见的就是_NSConcreteStackBlock，_NSConcreteMallocBlock，_NSConcreteGlobalBlock这3种(另外只在GC环境下还有3种使用的_NSConcreteFinalizingBlock，_NSConcreteAutoBlock，_NSConcreteWeakBlockVariable，本文暂不谈论这3种，有兴趣的看看官方文档)
以上介绍是Block的简要实现，接下来我们来仔细研究一下Block的捕获外部变量的特性以及__block的实现原理。
研究工具：clang 为了研究编译器的实现原理，我们需要使用 clang 命令。clang 命令可以将 Objetive-C 的源码改写成 C / C++ 语言的，借此可以研究 block 中各个特性的源码实现方式。该命令是
clang -rewrite-objc block.c####目录
1.</description></item><item><title>给 iOS 模拟器“安装” app 文件</title><link>https://new.halfrost.com/zh/ios_simulator_ios_sim/</link><pubDate>Thu, 18 Aug 2016 02:07:45 +0000</pubDate><guid>https://new.halfrost.com/zh/ios_simulator_ios_sim/</guid><description>前言 刚刚接触iOS的时候，我就一直很好奇，模拟器上面能不能直接安装app呢？如果可以，我们就直接在模拟器上面聊QQ和微信了。直到昨天和朋友们聊到了这个话题，没有想到还真的可以给模拟器“安装”app！
一.应用场景 先来谈谈是什么情况下，会有在模拟器上安装app的需求。
在一个大公司里，对源码的管理有严格的制度，非开发人员是没有权限接触到源码的。对苹果的开发证书管理也非常严格，甚至连开发人员也没有发布证书，证书只在持续集成环境或者Appstore产线里面，或者只在最后打包上架的人手上。
那么现在就有这样的需求，开发人员搭建好UI以后，要把开发完成的Alapha版给到UI设计师那边去评审，看看是否完全达到要求，达不到要求就需要打回来重做。
一般做法就是直接拿手机去安装一遍了。直接真机看效果。不过要是设计师和开发不在同一个地方的公司，一个在北京一个在上海，这种就没法安装了。源码又无法导出给设计师，让他运行一下Xcode跑一下模拟器。这个时候就比较麻烦了。(一般也没人遇到这么蛋疼的事情吧)
那么现在就有给模拟器安装app的需求了，那开发人员如何能把开发版的app给打包出来给其他模拟器安装呢？
二.解决办法 解决思路，想要别人的模拟器运行起我们开发的app，最简单的办法就是把我们DerivedData的数据直接拷贝到别人模拟器上面，就可以了。当然还要考虑到设计师也许并不会一些命令行命令，我们的操作越傻瓜越好。
1.拷贝本地的DerivedData里面的debug包 Mac的拷贝命令有cp和ditto，建议用ditto进行拷贝工作。
Usage: ditto [ &amp;lt;options&amp;gt; ] src [ ... src ] dst &amp;lt;options&amp;gt; are any of: -h print full usage -v print a line of status for each source copied -V print a line of status for every file copied -X do not descend into directories with a different device ID -c create an archive at dst (by default CPIO format) -x src(s) are archives -z gzip compress CPIO archive -j bzip2 compress CPIO archive -k archives are PKZip --keepParent parent directory name src is embedded in dst_archive --arch archVal fat files will be thinned to archVal multiple -arch options can be specified archVal should be one of &amp;#34;ppc&amp;#34;, &amp;#34;i386&amp;#34;, etc --bom bomFile only objects present in bomFile are copied --norsrc don&amp;#39;t preserve resource data --noextattr don&amp;#39;t preserve extended attributes --noqtn don&amp;#39;t preserve quarantine information --noacl don&amp;#39;t preserve ACLs --sequesterRsrc copy resources via polite directory (PKZip only) --nocache don&amp;#39;t use filesystem cache for reads/writes --hfsCompression compress files at destination if appropriate --nopreserveHFSCompression don&amp;#39;t preserve HFS+ compression when copying files --zlibCompressionLevel num use compression level &amp;#39;num&amp;#39; when creating a PKZip archive --password request password for reading from encrypted PKZip archiveDitto比cp命令更好的地方在于：</description></item><item><title>手把手教你给一个 iOS app 配置多个环境变量</title><link>https://new.halfrost.com/zh/ios_multienvironments/</link><pubDate>Wed, 10 Aug 2016 16:11:00 +0000</pubDate><guid>https://new.halfrost.com/zh/ios_multienvironments/</guid><description>前言 谈到多环境，相信现在大多公司都至少有2-3个app环境了，比如Test环境，UAT(User Acceptance Test)用户验收测试环境，Release环境等等。当需要开发打多个包的时候，一般常见做法就是直接代码里面修改环境变量，改完之后Archive一下就打包了。当然这种做法很正确，只不过不是很优雅很高效。如果搭建好了Jenkins(搭建教程)，我们利用它来优雅的打包。如果利用Jenkins来打包，我们就需要来给app来配置一下多个环境变量了。之后Jenkins分别再不同环境下自动集成即可。接下来，我们来谈谈常见的2种做法。
####目录
1.利用Build Configuration来配置多环境 2.利用xcconfig文件来配置多环境 3.利用Targets来配置多环境 一.利用Build Configuration来配置多环境 前言里面我们先谈到了需求，由于需要配置多个环境，并且多个环境都需要安装到手机上，那么可以配置Build Configuration来完成这个任务。如果Build Configuration还不熟悉的，可以先温习一下官方文档，新版文档链接在这里Build settings reference。
1. 新建Build Configuration 先点击Project里面找到Configuration，然后选择添加，这里新加一个Configuration。系统默认是2个，一个Debug，一个Release。这里我们需要选择是复制一个Debug还是Release。Release和Debug的区别是，Release是不能调试程序，因为默认是屏蔽了可调试的一些参数，具体可以看BuildSetting里面的区别，而且Release编译时有做编译优化，会比用Debug打包出来的体积更小一点。
这里我们选择一个Duplicate “Debug” Configuration，因为我们新的环境需要debug，添加完了之后就会多了一套Configuration了，这一套其实是包含了一些编译参数的配置集合。如果此时项目里面有cocopods的话，打开Configuration Set就会发现是如下的样子：
在我们自己的项目里面用了Pod，打开配置是会看到如下信息
注意：刚刚新建完Build Configuration之后，这时如果有pod，请立即执行一下
pod install pod安装完成之后会自动生成xcconfig文件，如果你手动新建这个xcconfig，然后把原来的debug和release对应的pod xcconfig文件内容复制进来，这样做是无效的，需要pod自己去生成xcconfig文件才能被识别到。
新建完Build Configuration，这个时候需要新建pod里面对应的Build Configuration，要不然一会编译会报错。如果没用pod，可以忽略一下这一段。
如下图新建一个对应之前Porject里面新建的Build Configuration
2. 新建Scheme 接下来我们要为新的Configuration新建一个编译Scheme。
新建完成之后，我们就可以编辑刚刚新建的Scheme，这里可以把Run模式和Archive都改成新建Scheme。如下图：
注意：如果是使用了Git这些协同工具的同学这里还需要把刚刚新建的Scheme共享出去，否则其他人看不到这个Scheme。选择“Manage Schemes”
3. 新建User-defined Build Settings 再次回到Project的Build Settings里面来，Add User-Defined Setting。
我们这里新加入2个参数，CustomAppBundleld是为了之后打包可以分开打成多个包，这里需要3个不同的Id，建议是直接在原来的Bundleld加上Scheme的名字即可。
CustomProductName是为了app安装到手机上之后，手机上显示的名字，这里可以按照对应的环境给予描述，比如测试服，UAT，等等。如下图。
这里值得提到的一点是，下面Pods的Build_DIR这些目录其实是Pods自己生成好的，之前执行过Pod install 之后，这里默认都是配置好的，不需要再改动了。
4. 修改info.plist文件 和 Images.xcassets 先来修改一下info.plist文件。
由于我们新添加了2个CustomAppBundleld 和 CustomProductName，这里我们需要把info.plist里面的Bundle display name修改成我们自定义的这个字典。编译过程中，编译器会根据我们设置好的Scheme去自己选择Debug，Release，TestRelease分别对应的ProductName。
我们还需要在Images.xcassets里面新添加2个New iOS App Icon，名字最好和scheme的名字相同，这样好区分。</description></item><item><title>手把手教你利用 Jenkins 持续集成 iOS 项目</title><link>https://new.halfrost.com/zh/ios_jenkins/</link><pubDate>Sat, 30 Jul 2016 23:04:00 +0000</pubDate><guid>https://new.halfrost.com/zh/ios_jenkins/</guid><description>前言 众所周知，现在App的竞争已经到了用户体验为王，质量为上的白热化阶段。用户们都是很挑剔的。如果一个公司的推广团队好不容易砸了重金推广了一个APP，好不容易有了一些用户，由于一次线上的bug导致一批的用户在使用中纷纷出现闪退bug，轻则，很可能前期推广砸的钱都白费了，重则，口碑不好，未来也提升不起用户量来了。静下心来分析一下问题的原因，无外乎就是质量没有过关就上线了。除去主观的一些因素，很大部分的客观因素我觉得可以被我们防范的。根据大神们提出的一套开发规范建议，CI + FDD，就可以帮助我们极大程度的解决客观因素。本文接下来主要讨论 Continuous Integration 持续集成（简称CI）
####目录
1.为什么我们需要持续集成 2.持续化集成工具——Jenkins 3.iOS自动化打包命令——xcodebuild + xcrun 和 fastlane - gym 命令 4.打包完成自动化上传 fir / 蒲公英 第三方平台 5.完整的持续集成流程 6.Jenkins + Docker 一. 为什么我们需要持续集成 谈到为什么需要的问题，我们就需要从什么是来说起。那什么是持续集成呢。
持续集成是一种软件开发实践：许多团队频繁地集成他们的工作，每位成员通常进行日常集成，进而每天会有多种集成。每个集成会由自动的构建（包括测试）来尽可能快地检测错误。许多团队发现这种方法可以显著的减少集成问题并且可以使团队开发更加快捷。
CI是一种开发实践。实践应该包含3个基本模块，一个可以自动构建的过程，自动编译代码，可以自动分发，部署和测试。一个代码仓库，SVN或者Git。最后一个是一个持续集成的服务器。通过持续集成，可以让我们通过自动化等手段高频率地去获取产品反馈并响应反馈的过程。
那么持续集成能给我们带来些什么好处呢？这里推荐一篇文章，文章中把Continuous integration (CI) and test-driven development (TDD)分成了12个步骤。然而带来的好处成倍增加，有24点好处。
我来说说用了CI以后带来的一些深有体会的优点。
1. 缩减开发周期，快速迭代版本 每个版本开始都会估算好开发周期，但是总会因为各种事情而延期。这其中包括了一些客观因素。由于产品线增多，迭代速度越来越快，给测试带来的压力也越来越大。如果测试都在开发完全开发完成之后再来测试，那就会影响很长一段时间。这时候由于集成晚就会严重拖慢项目节奏。如果能尽早的持续集成，尽快进入上图的12步骤的迭代环中，就可以尽早的暴露出问题，提早解决，尽量在规定时间内完成任务。
2. 自动化流水线操作带来的高效 其实打包对于开发人员来说是一件很耗时，而且没有很大技术含量的工作。如果开发人员一多，相互改的代码冲突的几率就越大，加上没有产线管理机制，代码仓库的代码质量很难保证。团队里面会花一些时间来解决冲突，解决完了冲突还需要自己手动打包。这个时候如果证书又不对，又要耽误好长时间。这些时间其实可以用持续集成来节约起来的。一天两天看着不多，但是按照年的单位来计算，可以节约很多时间！
3. 随时可部署 有了持续集成以后，我们可以以天为单位来打包，这种高频率的集成带来的最大的优点就是可以随时部署上线。这样就不会导致快要上线，到处是漏洞，到处是bug，手忙脚乱弄完以后还不能部署，严重影响上线时间。
4. 极大程度避免低级错误 我们可以犯错误，但是犯低级错误就很不应该。这里指的低级错误包括以下几点：编译错误，安装问题，接口问题，性能问题。 以天为单位的持续集成，可以很快发现编译问题，自动打包直接无法通过。打完包以后，测试扫码无法安装，这种问题也会立即被暴露出来。接口问题和性能问题就有自动化测试脚本来发现。这些低级问题由持续集成来暴露展现出来，提醒我们避免低级错误。
二. 持续化集成工具——Jenkins Jenkins 是一个开源项目，提供了一种易于使用的持续集成系统，使开发者从繁杂的集成中解脱出来，专注于更为重要的业务逻辑实现上。同时 Jenkins 能实施监控集成中存在的错误，提供详细的日志文件和提醒功能，还能用图表的形式形象地展示项目构建的趋势和稳定性。
根据官方定义，Jenkins有以下的用途：
构建项目 跑测试用例检测bug 静态代码检测 部署 关于这4点，实际使用中还是比较方便的： 1.</description></item><item><title>函数响应式编程(FRP)从入门到"放弃"——图解RACSignal篇</title><link>https://new.halfrost.com/zh/ios_rac_racsignal/</link><pubDate>Sun, 24 Jul 2016 10:54:00 +0000</pubDate><guid>https://new.halfrost.com/zh/ios_rac_racsignal/</guid><description>目录 1.RACSignal的创建 2.RACSignal的订阅 3.RACSignal各类操作 ####一.RACSignal的创建
1.创建单元信号
NSError *errorObject = [NSError errorWithDomain:@&amp;quot;Something wrong&amp;quot; code:500 userInfo:nil]; //基本的4种创建方法 RACSignal *signal1 = [RACSignal return:@&amp;quot;Some Value&amp;quot;]; RACSignal *signal2 = [RACSignal error:errorObject]; RACSignal *signal3 = [RACSignal empty]; RACSignal *signal4 = [RACSignal never]; 2.创建动态信号
RACSignal *signal5 = [RACSignal createSignal: ^RACDisposable *(id&amp;lt;RACSubscriber&amp;gt; subscriber) { [subscriber sendNext:@1]; [subscriber sendNext:@2]; [subscriber sendError:errorObject]; [subscriber sendCompleted]; return [RACDisposable disposableWithBlock:^{ }]; }]; 3.通过Cocoa桥接方式获得一个信号
RACSignal *signal6 = [view rac_signalForSelector:@selector(setFrame:)]; RACSignal *signal7 = [view rac_signalForControlEvents:UIControlEventTouchUpInside]; RACSignal *signal8 = [view rac_willDeallocSignal]; //KVO的原理实现 RACSignal *signal9 = RACObserve(view, backgroundColor); 4.</description></item><item><title>关于 IB_DESIGNABLE / IBInspectable 的那些需要注意的事</title><link>https://new.halfrost.com/zh/ios_ib_designable_ibinspectable/</link><pubDate>Fri, 22 Jul 2016 05:13:00 +0000</pubDate><guid>https://new.halfrost.com/zh/ios_ib_designable_ibinspectable/</guid><description>前言 IB_DESIGNABLE / IBInspectable 这两个关键字是在WWDC 2014年&amp;quot;What&amp;rsquo;s New in Interface Builder&amp;quot;这个Session里面，用Swift讲过一个例子。也是随着Xcode 6 新加入的关键字。
这两个关键字是用在我们自定义View上的，目前暂时只能用在UIView的子类中所以系统自带的原生的那些控件使用这个关键字都没有效果。
Live RenderingYou can use two different attributes—@IBDesignable and @IBInspectable—to enable live, interactive custom view design in Interface Builder. When you create a custom view that inherits from the UIView class or the NSView class, you can add the @IBDesignable attribute just before the class declaration. After you add the custom view to Interface Builder (by setting the custom class of the view in the inspector pane), Interface Builder renders your view in the canvas.</description></item><item><title>WWDC 2016 Session 笔记 - Xcode 8 Auto Layout 新特性</title><link>https://new.halfrost.com/zh/wwdc2016_xcode8autolayout_features/</link><pubDate>Sun, 17 Jul 2016 03:36:00 +0000</pubDate><guid>https://new.halfrost.com/zh/wwdc2016_xcode8autolayout_features/</guid><description>####目录
1.Incrementally Adopting Auto Layout 2.Design and Runtime Constraints 3.NSGridView 4.Layout Feedback Loop Debugging ####一.Incrementally Adopting Auto Layout Incrementally Adopting Auto Layout是什么意思呢？在我们IB里面布局我们的View的时候，我们并不需要一次性就添加好所有的constraints。我们可以一步步的增加constraints，简化我们的步骤，而且能让我们的设置起来更加灵活。
再谈新特性之前，先介绍一下这个特性对应的背景来源。
有这样一种场景，试想，我们把一个view放在父view上，这个时候并没有设置constraints，当我们运行完程序，就会出现下图的样子。
看上去一切都还正常。但是一旦当我们把设备旋转90°以后，就会出现下图的样子。
这个时候可以发现，这个View的长，宽，以及top和left的边距都没有发生变化。这时我们并没有设置constraints，这是怎么做到的呢？
在程序的编译期，Auto Layout的引擎会自动隐式的给View加上一些constraints约束，以保证View的大小不会发生变化。这个例子中，View被加上了top，left，width，height这4个约束。
如果我们需要更加动态的resize的行为，就需要我们在IB里面自定义约束了。现在问题就来了，有没有更好的方式来做这件事情？最好是能有一种不用约束的方法，也能达到简单的resize的效果。
现在这个问题有了解决办法。在Xcode8中，我们可以给View指定autoresizing masks，而不用去设置constraints。这就意味着我们可以不用约束，我们也能做到简单的resize的效果。
在Autolayout时代之前，可能会有人认出这种UI方式。这是一种Springs &amp;amp; Struts的UI。我们可以设定边缘约束(注：这里的约束并不是指的是Autolayout里面的constraints，是autoresizing masks里面的规则)，无论View的长宽如何变化，这些View都会跟随着设置了约束的view一起变化。
上述的例子中，Xcode 8 中在没有加如何constraint就可以做到旋转屏幕之后，View的边距并没有发生变化。这是怎么做到的呢？事实上，Xcode 8的做法是先取出autoresizing masks，然后把它转换成对应的constraints，这个转换的时机发生在Runtime期间。生成对应的constraints是发生在运行时，而不是编译时的原因是可以给我们开发者更加便利的方式为View添加更加细致的约束。
在View上，我们可以设置translatesAutoresizingMaskIntoConstraints属性。
translatesAutoresizingMaskIntoConstraints == true 假设如果View已经在Interface Builder里面加过constraints，“Show the Size inspector”面板依旧会和以前一样。点击View，查看给它加的所有的constraints，这个时候Autoresizing masks就被忽略了，而且translatesAutoresizingMask的属性也会变成false。如下图，我们这个时候在“Show the Size inspector”面板上面就已经看不到AutoresizingMask的设置面板了。
上图就是在Autolayout时代之前，我们一直使用的是autoresizing masks，但是Autolayout时代来临之后，一旦勾选上了这个Autolayout，之前的AutoresizingMask也就失效了。
回到我们最原始的问题上来，Xcode 8 现在针对View可以支持增量的适用Autolayout。这就意味着我们可以从AutoresizingMask开始，先做简单的resize的工作，然后如果有更加复杂的需求，我们再加上适当的约束constraints来进行适配。简而概之，Xcode 8 Autolayout ≈ AutoresizingMask + Autolayout 。
接下来用一个demo的例子来说明一下Xcode 8 Autolayout新特性。</description></item><item><title>函数响应式编程 ( FRP ) 从入门到"放弃"——基础概念篇</title><link>https://new.halfrost.com/zh/functional_reactive_programming_concept/</link><pubDate>Mon, 11 Jul 2016 01:24:00 +0000</pubDate><guid>https://new.halfrost.com/zh/functional_reactive_programming_concept/</guid><description>前言 研究ReactiveCocoa一段时间了，是时候总结一下学到的一些知识了。
一.函数响应式编程 说道函数响应式编程，就不得不提到函数式编程，它们俩到底有什么关系呢？今天我们就详细的解析一下他们的关系。
现在有下面4个概念，需要我们理清一下它们之间的关系：
面向对象编程 Object Oriented Programming
响应式编程 Reactive Programming
函数式编程 Functional Programming
函数响应式编程 Functional Reactive Programming
我们先来说说什么是函数式编程Functional Programming，我们先来看看wikipedia上的相关定义：
Functional Programming is a programming paradigm
treats computation as the evaluation of mathematical functions. avoids changing-state and mutable data 总结一下函数式编程具有以下几个特点：
函数是&amp;quot;第一等公民&amp;rdquo; 闭包和高阶函数 不改变状态(由此延伸出”引用透明”的概念) 递归 只用&amp;quot;表达式&amp;rdquo;，不用&amp;quot;语句&amp;rdquo;，没有副作用 接下来我们依次说明一下这些特点。
一. 函数是&amp;quot;第一等公民&amp;rdquo; 所谓&amp;quot;第一等公民&amp;rdquo;（first class），指的是函数与其他数据类型一样，处于平等地位，可以赋值给其他变量，也可以作为参数，传入另一个函数，或者作为别的函数的返回值。
一等函数的理念可以追溯到 Church 的 lambda 演算 (Church 1941; Barendregt 1984)。此后，包括 Haskell，OCaml，Standard ML，Scala 和 F# 在内的大量 (函数式) 编程语言都不同程度地借鉴了这个概念。</description></item><item><title>WWDC 2016 Session 笔记 - iOS 10 UICollectionView 新特性</title><link>https://new.halfrost.com/zh/wwdc2016_ios10_uicollectionview_new_features/</link><pubDate>Sun, 03 Jul 2016 09:02:00 +0000</pubDate><guid>https://new.halfrost.com/zh/wwdc2016_ios10_uicollectionview_new_features/</guid><description>前言 关于 iOS 10 UICollectionView的新特性，主要还是体现在如下3个方面
顺滑的滑动体验 现在基本上人人都离不开手机，手机的app也每天都有人在用。一个app的好坏由它的用户体验决定。在可以滑动的视图里面，必须要更加丝滑柔顺才能获得用户的青睐。这些UICollectionView的新特性可以让你们的app比原来更加顺滑，而且这些特性只需要你加入少量的代码即可达到目的。 针对self-sizing的改进 self-sizing的API在iOS8的时候被引进，iOS10中加入更多特性使cell更加容易去适配。 Interactive reordering重排 这个功能在iOS9的时候介绍过了，苹果在iOS 10的API里面大大增强了这一功能。 ####目录
1.UICollectionViewCell顺滑的滑动体验 2.UICollectionViewCell的Pre-Fetching预加载 3.UITableViewCell的Pre-Fetching预加载 4.针对self-sizing的改进 5.Interactive Reordering 6.UIRefreshControl 一. UICollectionViewCell顺滑的滑动体验 众所周知，iOS设备已良好的用户体验赢得了广大的用户群。iOS系统在用户点击屏幕会立即做出响应。而且很大一部分的操作是来自于用户的滑动操作。所以滑动的顺滑是使用户沉浸在app中享受的必要条件。接下来我们就谈谈iOS 10 中增加了那些新特性。
我们先来看一下之前 UICollectionView 的体验，假设我们每个cell都是简单的蓝色，实际开发app中，cell会比这复杂很多。 我们先生成100个cell。当用户滑动不是很快的时候，还感觉不出来卡顿，当用户大幅度滑动，整个UICollectionView的卡顿就很明显了。如果整个cell的DataSource又是从网络加载的，那就更加卡顿了。效果如下图。
如果这种app上架，用户使用过后，很可能就直接给1星评价了。但是为什么会造成这种问题呢？我们来分析一下，我们模拟一下系统如何处理重用机制的，效果如下图
在上图中，我们可以看出，当cell准备加载进屏幕的时候，整个cell都已经加载完成，等待在屏幕外面了。而且更重要的是，在屏幕外面等待加载的cell是整整一行！这一行的cell都已经加载完数据。这是UICollectionView在用户大幅度滑动时卡顿的根本原因。用专业的术语来说，掉帧。
接下来我们就来详细的说说掉帧的问题。
当今的用户是很挑剔的，用户需要一个很顺滑的体验，只要有一点卡顿，很可能一言不合就卸载app了。要想用户感觉不到卡顿，那么我们的app必须帧率达到60帧/秒。用数学换算一下就是每帧16毫秒就必须刷新一次。
我们用图标来分析一下掉帧的问题。下面会出现2种不同的帧。
第一种情况，下图是当用户轻微的上下小幅度滑动。这个时候每个cell的加载压力都不大，iOS针对这种情况，已经做了很好的优化了，所以用户感觉不到任何卡顿。这种情况是不会掉帧，用户也希望能使用如此顺滑的app。
第二种情况，当用户大幅度滑动，每个cell加载的压力很大，也许需要网络请求，也许需要读取数据库，而且每次都加载一行cell出来，这样每个cell的加载时间都增加了，加载一行的总时间也就大大增加了，如下图所示。这样，不仅仅当前帧在加载cell，总的时间还会挤压到下一帧的时间里面去。这种情况下，用户就感觉到了卡顿了。
我们换种方式在说明一下2种情况下掉帧的情况。我们用下图的标准来衡量一下上面2种情况。下图分为2部分，上面红色的区域，就是表示掉帧的区域，因为高于16ms。红色和绿色区域的分界线就在16ms处。y轴我们表示的是CPU在主线程中花费的时间。x轴表示的是在用户滑动中发生的刷新事件。
针对上述掉帧的情况，绘制出实验数据，如下图。值得我们关注的是，曲线是很曲折的，非常的不平滑。当用户大幅度滑动的时候，峰值超过了16ms，当用户慢速滑动的时候，帧率又能保持在比较顺滑的区域。处于绿色区域内的cell加载压力都是很小的。这就是时而掉帧时而顺滑的场景。这种场景下，用户体验是很糟糕的。
那怎么解决这么问题的呢？我们来看下图：
上图中的曲线我们看着就很平缓了，而且这种情况也不会出现掉帧的情况了，每个滑动中的时间都能达到60帧了。这是怎样做到的呢？因为把每个cell的加载事件都平分了，每个cell不会再出现很忙和很闲的两个极端。这样我们就取消了之前的波峰和波谷。从而让该曲线达到近乎水平的直线。
如何让每个cell都分摊加载任务的压力？这就要谈到新的cell的生命周期了。
先来看看老的 UICollectionViewCell的声明周期。当用户滑动屏幕，屏幕外有一个cell准备加载显示进来。
这个时候我们把这个cell从reuse队列里面拿出来，然后调用prepareForReuse方法。这个方法就给了cell时间，用来重置cell，重置状态，刷新cell，加载新的数据。
再滑动，我们就会调用cellForItemAtIndexPath方法了。这个方法里面就是我们开发者自定义的填充cell的方式了。这里会填充data model，然后赋值给cell，再把cell返回给iOS系统。
当cell马上要进入屏幕的时候，就会调用willDisplayCell的方法。这个方法给了我们app最后一次机会，为cell进入屏幕做最后的准备工作。执行完willDisplayCell之后，cell就进入屏幕了。
当cell完全离开屏幕之后，就会调用didEndDisplayingCell方法。以上就是在iOS10之前的整个UICollectionViewCell的生命周期。
接下来我们就来看看iOS 10的UICollectionViewCell生命周期是怎么样的。
这里还是和iOS9一样的，当用户滑动UICollectionView的时候，需要一个cell，我们就从reuse队列里面拿出一个cell，并调用prepareForReuse方法。注意调用这个方法的时间，当cell还没有进入屏幕的时候，就已经提前调用这个方法了。注意对比和iOS 9的区别，iOS 9 是在cell上边缘马上进入屏幕的时候才调用方法，而这里，cell整个生命周期都被提前了，提前到cell还在设备外面的时候。
这里还是和之前一样，在cellForItemAtIndexPath中创建cell，填充数据，刷新状态等等操作。注意，这里生命周期也比iOS 9提前了。
用户继续滑动，这个时候就有不同了！
这个时候我们并不去调用willDisplayCell方法了！这里遵循的原则是，何时去显示，何时再去调用willDisplayCell。
当cell要马上就需要显示的时候，我们再调用willDisplayCell方法。
当整个cell要从UICollectionView的可见区域消失的时候，这个时候会调用didEndDisplayingCell方法。接下来发生的事情和iOS9一样，cell会进入重用队列中。
如果用户想要显示某个cell，在iOS 9 当中，cell只能从重用队列里面取出，再次走一遍生命周期。并调用cellForItemAtIndexPath去创建或者生成一个cell。
在iOS 10 当中，系统会把cell保持一段时间。在iOS中，如果用户把cell滑出屏幕后，如果突然又想回来，这个时候cell并不需要再走一段的生命周期了。只需要直接调用willDisplayCell就可以了。cell就又会重新出现在屏幕中。这就是iOS 10 的整个UICollectionView的生命周期。</description></item><item><title>WWDC 2016 Session 笔记 - iOS 10 推送 Notification 新特性</title><link>https://new.halfrost.com/zh/wwdc2016_ios10_notification_new_features/</link><pubDate>Sun, 26 Jun 2016 06:22:00 +0000</pubDate><guid>https://new.halfrost.com/zh/wwdc2016_ios10_notification_new_features/</guid><description>前言 在今年6月14号苹果WWDC开发者大会上，苹果带来了新的iOS系统——iOS 10。苹果为iOS 10带来了十大项更新。苹果高级副总裁Craig Federighi称此次对iOS的更新是“苹果史上最大的iOS更新”。
新的屏幕通知查看方式：苹果为iOS 10带来了全新的通知查看功能，即抬起iPhone的屏幕，用户就能看到目前的通知和更新情况。 苹果将Siri开放给第三方开发者: 现在用户可以让Siri实现更多的功能，例如让Siri向自己的联系人发送微信信息等。目前Siri可以直接支持的应用有微信、WhatsApp以及Uber、滴滴、Skype等。 Siri将会更加智能：Siri将拥有更多对语境的意识。基于用户的地点、日历、联系人、联系地址等，Siri会做出智能建议。Siri将越来越成为一个人工智能机器人，具备深度学习功能。 照片应用更新：基于深度学习技术，iOS 10对照片应用有比较大的更新。iOS 10对照片的搜索能力进一步增强，可以检测到新的人物和景色。未来的iPhone能够将相关的照片组织在一起，比如某次旅行的照片、某个周末的照片，并且能够进行自动编辑。iOS 10照片还新增了一个“记忆”标签。 苹果地图：有点类似Siri和照片的更新，苹果地图也增加了很多预测功能，例如苹果地图能够将提供附近的餐厅建议。苹果地图的界面也得到了重新设计，更加的简洁，并增加了交通实时信息。新的苹果地图还将整合在苹果CarPlay中，将为用户提供turn-by-turn导航功能。和Siri一样，地图也将开放给开发者。 苹果音乐：苹果音乐的界面得到了更新，界面会更加简洁、支持多任务，增加最近播放列表。苹果音乐现在已经有1500万付费用户。 HomeKit：iOS 10新增智能家庭应用，支持一键场景模式，HomeKit可以与Siri相连接。 苹果电话：苹果更新了电话功能，来电时可以区别出骚扰电话。
iMesseage：在iMessage方面，用户可以直接在文本框内发送视频、链接，分享实时照片。另外，苹果还增添了表情预测功能，打出的文字若和表情相符，将会直接推荐相关表情。 以下是我关于关于iOS 10中变化比较大的推送通知的学习笔记。
####目录
1.Notification User Interface 2.Media Attachments 3.Customize user interface 4.Customize Actions 一. Notification User Interface 让我们先来看看用户推送在iOS X中的样子，如下图
上图这是在锁屏界面下的推送。支持抬起手机唤醒功能。
上图是Banner，可以看到这个推送更加的易读，并且包含更多的内容。
上图是通知中心。从上面三种图可以看到，它们都长一个样。
在iOS 8 中，我们可以给推送增加用户操作，这样使推送更加具有交互性，并且允许用户去处理用户推送更加的迅速。到了iOS 9 中，苹果又再次增加了快速回复功能，进一步的提高了通知的响应性。开发者可以允许用户通过点击推送，并用文字进行回复。再就到了iOS 10 中，推送变得更加给力。因为在iOS X中，推送对iOS系统来说，是很重要的一部分。在日常使用中，我们会经常和推送打交道。推送是我们和设备进行互动非常重要的方式。
在iOS X 中，你可以按压推送，推送就会被展开，展示出更加详细的用户界面。展示出来的详细界面对用户来说，提供了更加有用的信息。用户可以通过点击下面的按钮，来处理一些事件，并且推送的详细界面也会跟着用户的操作进行更新UI界面。
iOS 8 中iMessage支持了快速回复功能，但是你只能看见一条信息，并且你也只能回复一条信息。但是在iOS X中，你可以展开推送，这个时候你就可以看到整个对话的内容了。你可以等待你的朋友回复，你再回复他，并且可以回复很多条。
以上就是iOS X的强大功能。以上的所有功能都能通过iOS X的新API来实现。所有的新特性都能在我们开发者开发的app里面有所体现。
二. Media Attachments 如果经常使用iMessage的朋友们，就会经常收到一些信息，附带了一些照片或者视频，所以推送中能附带这些多媒体是非常重要的。如果推送中包含了这些多媒体信息，可以使用户不用打开app，不用下载就可以快速浏览到内容。众所周知，推送通知中带了push payload，及时去年苹果已经把payload的size提升到了4k bites，但是这么小的容量也无法使用户能发送一张高清的图片，甚至把这张图的缩略图包含在推送通知里面，也不一定放的下去。在iOS X中，我们可以使用新特性来解决这个问题。我们可以通过新的service extensions来解决这个问题。</description></item><item><title>iOS 如何优雅的处理“回调地狱 Callback hell ”(二) —— 使用 Swift</title><link>https://new.halfrost.com/zh/ios_callback_hell_swift/</link><pubDate>Wed, 22 Jun 2016 06:45:00 +0000</pubDate><guid>https://new.halfrost.com/zh/ios_callback_hell_swift/</guid><description>####前言 在上篇中，我谈到了可以用promise来解决Callback hell的问题，这篇我们换一种方式一样可以解决这个问题。
我们先分析一下为何promise能解决多层回调嵌套的问题，经过上篇的分析，我总结也一下几点：
1.promise封装了所有异步操作，把异步操作封装成了一个“盒子”。 2.promise提供了Monad，then相当于flatMap。 3.promise的函数返回对象本身，于是就可形成链式调用
好了，既然这些能优雅的解决callback hell，那么我们只要能做到这些，也一样可以完成任务。到这里大家可能就已经恍然大悟了，Swift就是完成这个任务的最佳语言！Swift支持函数式编程，分分钟就可以完成promise的基本功能。
####一.利用Swift特性处理回调Callback hell
我们还是以上篇的例子来举例，先来描述一下场景： 假设有这样一个提交按钮，当你点击之后，就会提交一次任务。当你点下按钮的那一刻，首先要先判断是否有权限提交，没有权限就弹出错误。有权限提交之后，还要请求一次，判断当前任务是否已经存在，如果存在，弹出错误。如果不存在，这个时候就可以安心提交任务了。
那么代码如下：
func requestAsyncOperation(request : String , success : String -&amp;gt; Void , failure : NSError -&amp;gt; Void) { WebRequestAPI.fetchDataAPI(request, success : { result in WebOtherRequestAPI.fetchOtherDataAPI ( result , success : {OtherResult in [self fulfillData:OtherResult]; let finallyTheParams = self.transformResult(OtherResult) TaskAPI.fetchOtherDataAPI ( finallyTheParams , success : { TaskResult in let finallyTaskResult = self.transformTaskResult(TaskResult) success(finallyTaskResult) }, failure:{ TaskError in failure(TaskError) } ) },failure : { ExistError in failure(ExistError) } ) } , failure : { AuthorityError in failure(AuthorityError) } ) } 接下来我们就来优雅的解决上述看上去不好维护的Callback hell。</description></item><item><title>如何快速给自己构建一个温馨的"家"——用 Jekyll 搭建静态博客</title><link>https://new.halfrost.com/zh/jekyll/</link><pubDate>Mon, 20 Jun 2016 10:17:00 +0000</pubDate><guid>https://new.halfrost.com/zh/jekyll/</guid><description>前言 我相信，每个程序员都有一个愿望，都想有一个属于自己的&amp;quot;家&amp;rdquo;——属于自己的博客，专属的网站。在自己的“家”中，可以和志同道合的兄弟一起分享和讨论任何技术，谈天说地。更重要的是可以当做自己的技术积累，提升自己实力。那么接下来就来说说我博客搭建过程。
目录： 本地搭建Jekyll 开发或者选择Jekyll主题 使用Github Pages服务 申请个人域名 给博客增加访客评论功能 申请&amp;quot;小绿锁&amp;quot;HTTPS 日后维护 一.本地搭建Kekyll Jekyll是什么？它是一个简单静态博客生成工具，相对于动态博客。
简单。因为它是不需要数据库的，通过markdown编写静态文件，生成Html页面，它的优点是提升了页面的响应速度，并且让博主可以只专注于写文章，不用再去考虑如何排版。 静态。Markdown（或 Textile）、Liquid 和 HTML &amp;amp; CSS 构建可发布的静态网站。 博客支持。支持自定义地址、博客分类、页面、文章以及自定义的布局设计。 //使用gem安装Jekyllgem install jekyll//使用Jekyll创建你的博客站点jekyll new blog #创建你的站点//开启Jekyll服务//进入blog目录,记得一定要进入创建的目录，否则服务无法开启cd blog jekyll serve #启动你的http服务 本地服务开启后，Jekyll服务默认端口是4000，所以我打开浏览器，输入：http://localhost:4000 即可访问
到这里一个简单的博客页面就会显示出来了。
关于jekyll其他一些命令的用法如下:
$ jekyll build# =&amp;gt; 当前文件夹中的内容将会生成到 ./_site 文件夹中。$ jekyll build --destination &amp;lt;destination&amp;gt;# =&amp;gt; 当前文件夹中的内容将会生成到目标文件夹&amp;lt;destination&amp;gt;中。$ jekyll build --source &amp;lt;source&amp;gt; --destination &amp;lt;destination&amp;gt;# =&amp;gt; 指定源文件夹&amp;lt;source&amp;gt;中的内容将会生成到目标文件夹&amp;lt;destination&amp;gt;中。$ jekyll build --watch# =&amp;gt; 当前文件夹中的内容将会生成到 ./_site 文件夹中，# 查看改变，并且自动再生成。$ jekyll serve# =&amp;gt; 一个开发服务器将会运行在 http://localhost:4000/# Auto-regeneration（自动再生成文件）: 开启。使用 `--no-watch` 来关闭。$ jekyll serve --detach# =&amp;gt; 功能和`jekyll serve`命令相同，但是会脱离终端在后台运行。# 如果你想关闭服务器，可以使用`kill -9 1234`命令，&amp;#34;1234&amp;#34; 是进程号（PID）。# 如果你找不到进程号，那么就用`ps aux | grep jekyll`命令来查看，然后关闭服务器。[更多](http://unixhelp.</description></item><item><title>iOS 如何优雅的处理“回调地狱 Callback hell ”(一) —— 使用 PromiseKit</title><link>https://new.halfrost.com/zh/ios_callback_hell_promisekit/</link><pubDate>Fri, 10 Jun 2016 03:51:00 +0000</pubDate><guid>https://new.halfrost.com/zh/ios_callback_hell_promisekit/</guid><description>####前言 最近看了一些Swift关于封装异步操作过程的文章，比如RxSwift，RAC等等，因为回调地狱我自己也写过，很有感触，于是就翻出了Promise来研究学习一下。现将自己的一些收获分享一下，有错误欢迎大家多多指教。
####目录
1.PromiseKit简介 2.PromiseKit安装和使用 3.PromiseKit主要函数的使用方法 4.PromiseKit的源码解析 5.使用PromiseKit优雅的处理回调地狱 ####一.PromiseKit简介 PromiseKit是iOS/OS X 中一个用来处理异步编程框架。这个框架是由Max Howell(Mac下Homebrew的作者，传说中因为&amp;quot;不会&amp;quot;写反转二叉树而没有拿到Google offer)大神级人物开发出来的。
在PromiseKit中，最重要的一个概念就是Promise的概念，Promise是异步操作后的future的一个值。
A promise represents the future value of an asynchronous task. A promise is an object that wraps an asynchronous task
Promise也是一个包装着异步操作的一个对象。使用PromiseKit，能够编写出整洁，有序的代码，逻辑简单的，将Promise作为参数，模块化的从一个异步任务到下一个异步任务中去。用PromiseKit写出的代码就是这样：
[self login].then(^{ // our login method wrapped an async task in a promise  return [API fetchData]; }).then(^(NSArray *fetchedData){ // our API class wraps our API and returns promises  // fetchedData returned a promise that resolves with an array of data  self.</description></item><item><title>手把手教你从 Core Data 迁移到 Realm</title><link>https://new.halfrost.com/zh/ios_coredata_to_realm/</link><pubDate>Thu, 02 Jun 2016 08:03:00 +0000</pubDate><guid>https://new.halfrost.com/zh/ios_coredata_to_realm/</guid><description>前言 看了这篇文章的标题，也许有些人还不知道Realm是什么，那么我先简单介绍一下这个新生的数据库。号称是用来替代SQLite 和 Core Data的。Realm有以下优点：
使用方便 Realm并不是基于SQLite的对象关系映射数据库。它是使用自己的持久化引擎，为简单和速度而生。用户们说，他们在数分钟之内就上手了Realm，构建一个app只需要数小时，每个app开发时间至少节约数周的时间。
快 Realm比其他的对象关系映射型数据库(Object Relational Mapping)，甚至比原生的SQLite更加快，这都得益于它零拷贝的设计。看看iOS用户和Android用户都是怎么评价它的快的Twitter
跨平台 Realm 支持 iOS 和 OS X (Objective‑C &amp;amp; Swift) 和Android。你可以通过使用相同的model，共享Realm文件到各个平台，Java，Swift，Objective-C。并且在全平台可以使用相同的业务逻辑 优秀的特性 Realm支持先进的特性，如加密，图形查询，轻松的迁移。Realm的API是一个非常适合打造高响应的应用程​​序，并且Realm为我们提供方便的组件，以轻松构建复杂的用户界面
值得信任 Realm已经获得了银行，医疗保健提供商，复杂的企业app，星巴克这些产品的青睐。
社区驱动 Realm是Github上星标最多的数据库里面排名第四，仅次于Java 和 Cocoa 的repos。除了核心工程之外，Realm的社区已经编译了上百个app插件和组件
支持 可以从Realm公司快速获得官方的答案，去编译和支持你的数据库。Realm的团队会在Github, StackOverflow, &amp;amp; Twitter回答大家的各种问题
下面再发3张令人惊喜的性能对比图
上图是每秒能在20万条数据中进行查询后count的次数。realm每秒可以进行30.9次查询后count。SQLite仅仅只有每秒13.6次查询后的count，相对于Core Data只有可怜的1。
在20万条中进行一次遍历查询，数据和前面的count相似：Realm一秒可以遍历20万条数据31次，而RCore Data只能进行两次查询。 SQLite也只有14次而已。
这是在一次事务每秒插入数据的对比，Realm每秒可以插入9.4万条记录，在这个比较里纯SQLite的性能最好，每秒可以插入17.8万条记录。然而封装了SQLite的FMDB的成绩大概是Realm的一半，4.7万，Core Data就更低了，只有可怜的1.8万。
从以上3张图可以看出Realm优秀的特性。那么我们开始使用Realm吧。第一步就是把本地的数据库换成Realm。
下面是我翻译的一篇手把手教程，那么让我们赶紧通过教程，来把Core Data迁移到Realm吧。
原文 译文 把一个使用core data框架作为数据库存储方式的app，迁移到Realm的确是一件很容易的事情。如果你现在有一个已经用了Core Data的app，并且考虑换成Realm，这个手把手教程正适合你！
很多开发者在用户界面，高度集成了Core Data(有时可能有上千行代码),这时很多人会告诉你转换Core Data到Realm可能会花数小时。Core Data和Realm两者都是把你的数据当成Object看待，所以迁移通常是很直接的过程:把你已经存在的Core Data的代码重构成使用Realm API的过程是很简单的。</description></item><item><title>iOS app 旧貌换新颜(一) — Launch Page 让 Logo "飞"出屏幕</title><link>https://new.halfrost.com/zh/ios_launchpage_logo_fly/</link><pubDate>Tue, 24 May 2016 14:56:00 +0000</pubDate><guid>https://new.halfrost.com/zh/ios_launchpage_logo_fly/</guid><description>####前言 当今是个看脸的世界，一个app的颜值可能会决定用户的使用次数，icon的颜值更可能决定了用户是否回去下载，如果太丑，放在手机桌面都难看，那用户很可能就直接卸载了。所以漂亮的界面UI + 合理的用户体验UX/UE也会很大程度决定用户的黏性。最近由于公司的app准备美化一下界面，提升性能，所以我就想把美化过程中可以和大家分享的东西都整理整理，拿出来也和大家一起分享学习。这个“旧貌换新颜”我就写成一个系列吧，相信美化的道路是永无止境的！(场外音:自己又给自己开了一个巨坑)
####一.灵感的来源 也许有些人看了文章的标题并不一定完全懂是啥意思，其实设计这个的来源源自于我在微博上看到的一个动图，很生动，形象。
一个呆萌的大叔点开Twitter客户端，启动界面有一个动效，就是他们的logo直接“飞”出屏幕，打在了他的脸上。这个效果我当时看了就觉得很有趣。很多应用每次启动之后都是直接进去，或者先展示一个几秒的广告页。其实要是加一个这种启动特性，感觉也挺不错。
####二.动画原理 接下来说一下上面那个启动特效的原理，其实原理很简单:app在启动之后，先加载一个View，上面放了我们的logo，然后在做一个放大的UIView的动画就好了。接下来看看我的做法吧。
####三.准备工具 PS + AI 或者 Sketch + PaintCode 这个可能有人问了，怎么突然还需要这些作图的工具。其实大家也可以加载一个logo图片放在view上，一样可以实现。不过老板觉得加载一张图片如果太高清会占app大小，能尽量程序画出来的，就让程序画出来。对于不规则复杂的图形，就只好用上面这一套组合工具了。
PS主要是把logo抠出来
AI和Sketch是为了把抠出来的logo用钢笔工具，进行描点，导出路径。
最后PaintCode就是把路径转换成UIBezierPath(PaintCode这个软件很厉害，可以直接把SVG里面的路径直接转换成对应的Swift或者Objective-C代码)(后来我发现其实只要用PaintCode一个软件就可以完成上面所有功能了，它也可以直接用钢笔工具画路径)
####四.开始制作 1.首先用PS把Logo图抠出来，保存成图片。 2.然后打开Sketch，导入刚刚的Logo图片。
3.选择左上角的“Insert”-“Vector”钢笔工具，依次连接Logo图标的各个顶点
4.然后在每段顶点之间，加新的锚点，钢笔工具会出现+号。在软件的右侧，会出现下面这个面板
通过拖拉这些你加出来的点，可以使路径完全吻合Logo复杂的外形。拖过一番拖拽之后，就应该成下面这个图的样子了。
5.接下来我们就选择左边面板上面有一个Page面板
选一下刚刚描出来的Path，右下角会出现一个Export面板
这个时候我们选择导出SVG文件
SVG可缩放矢量图形（Scalable Vector Graphics）是基于 可扩展标记语言（XML），用于描述二维矢量图形的一种图形格式。SVG是W3C(&amp;ldquo;World Wide Web ConSortium&amp;rdquo; 即 &amp;quot; 国际互联网标准组织&amp;rdquo;)在2000年8月制定的一种新的二维矢量图形格式，也是规范中的网络矢量图形标准。SVG严格遵从XML语法，并用文本格式的描述性语言来描述图像内容，因此是一种和图像分辨率无关的矢量图形格式
其实这里有一个小插曲，绘制路径的时候，其实我用的是AI描点的，之后导出SVG给PaintCode，居然不识别我的路径。后来网上问了问，大神要我换Sketch试试，然后就行了。后来我比较了一下Sketch和AI导出的SVG有什么不同，才发现，我之前AI导出的，加了几个图层，把路径盖住了。用AI绘制路径的方法和Sketch的差不多，如下图。
6.把之前导出的SVG文件导入到PaintCode中，下面会自动生成Objective-C代码
把生成的这些代码复制出来。
//// Color Declarations UIColor* color1 = [UIColor colorWithRed: 0.521 green: 0.521 blue: 0.521 alpha: 1]; //// Bezier Drawing //// Page-1 { //// Bezier 2 Drawing UIBezierPath* bezier2Path = UIBezierPath.</description></item><item><title>微信,QQ 这类 IM app 怎么做——谈谈 Websocket</title><link>https://new.halfrost.com/zh/ios_weixin_qq_websocket/</link><pubDate>Sun, 15 May 2016 02:55:00 +0000</pubDate><guid>https://new.halfrost.com/zh/ios_weixin_qq_websocket/</guid><description>####前言 关于我和WebSocket的缘：我从大二在计算机网络课上听老师讲过之后，第一次使用就到了毕业之后的第一份工作。直到最近换了工作，到了一家是含有IM社交聊天功能的app的时候，我觉得我现在可以谈谈我对WebSocket/Socket的一些看法了。要想做IM聊天app，就不得不理解WebSocket和Socket的原理了，听我一一道来。
####目录
1.WebSocket使用场景 2.WebSocket诞生由来 3.谈谈WebSocket协议原理 4.WebSocket 和 Socket的区别与联系 5.iOS平台有哪些WebSocket和Socket的开源框架 6.iOS平台如何实现WebSocket协议 #####一.WebSocket的使用场景 1.社交聊天
最著名的就是微信，QQ，这一类社交聊天的app。这一类聊天app的特点是低延迟，高即时。即时是这里面要求最高的，如果有一个紧急的事情，通过IM软件通知你，假设网络环境良好的情况下，这条message还无法立即送达到你的客户端上，紧急的事情都结束了，你才收到消息，那么这个软件肯定是失败的。
2.弹幕
说到这里，大家一定里面想到了A站和B站了。确实，他们的弹幕一直是一种特色。而且弹幕对于一个视频来说，很可能弹幕才是精华。发弹幕需要实时显示，也需要和聊天一样，需要即时。
3.多玩家游戏
4.协同编辑
现在很多开源项目都是分散在世界各地的开发者一起协同开发，此时就会用到版本控制系统，比如Git，SVN去合并冲突。但是如果有一份文档，支持多人实时在线协同编辑，那么此时就会用到比如WebSocket了，它可以保证各个编辑者都在编辑同一个文档，此时不需要用到Git，SVN这些版本控制，因为在协同编辑界面就会实时看到对方编辑了什么，谁在修改哪些段落和文字。
5.股票基金实时报价
金融界瞬息万变——几乎是每毫秒都在变化。如果采用的网络架构无法满足实时性，那么就会给客户带来巨大的损失。几毫秒钱股票开始大跌，几秒以后才刷新数据，一秒钟的时间内，很可能用户就已经损失巨大财产了。
6.体育实况更新
全世界的球迷，体育爱好者特别多，当然大家在关心自己喜欢的体育活动的时候，比赛实时的赛况是他们最最关心的事情。这类新闻中最好的体验就是利用Websocket达到实时的更新！
7.视频会议/聊天
视频会议并不能代替和真人相见，但是他能让分布在全球天涯海角的人聚在电脑前一起开会。既能节省大家聚在一起路上花费的时间，讨论聚会地点的纠结，还能随时随地，只要有网络就可以开会。
8.基于位置的应用
越来越多的开发者借用移动设备的GPS功能来实现他们基于位置的网络应用。如果你一直记录用户的位置(比如运行应用来记录运动轨迹)，你可以收集到更加细致化的数据。
9.在线教育
在线教育近几年也发展迅速。优点很多，免去了场地的限制，能让名师的资源合理的分配给全国各地想要学习知识的同学手上，Websocket是个不错的选择，可以视频聊天、即时聊天以及其与别人合作一起在网上讨论问题&amp;hellip;
10.智能家居
这也是我一毕业加入的一个伟大的物联网智能家居的公司。考虑到家里的智能设备的状态必须需要实时的展现在手机app客户端上，毫无疑问选择了Websocket。
11.总结
从上面我列举的这些场景来看，一个共同点就是，高实时性！
#####二.WebSocket诞生由来 1.最开始的轮询Polling阶段
这种方式下，是不适合获取实时信息的，客户端和服务器之间会一直进行连接，每隔一段时间就询问一次。客户端会轮询，有没有新消息。这种方式连接数会很多，一个接受，一个发送。而且每次发送请求都会有Http的Header，会很耗流量，也会消耗CPU的利用率。
2.改进版的长轮询Long polling阶段
长轮询是对轮询的改进版，客户端发送HTTP给服务器之后，有没有新消息，如果没有新消息，就一直等待。当有新消息的时候，才会返回给客户端。在某种程度上减小了网络带宽和CPU利用率等问题。但是这种方式还是有一种弊端：例如假设服务器端的数据更新速度很快，服务器在传送一个数据包给客户端后必须等待客户端的下一个Get请求到来，才能传递第二个更新的数据包给客户端，那么这样的话，客户端显示实时数据最快的时间为2×RTT（往返时间），而且如果在网络拥塞的情况下，这个时间用户是不能接受的，比如在股市的的报价上。另外，由于http数据包的头部数据量往往很大（通常有400多个字节），但是真正被服务器需要的数据却很少（有时只有10个字节左右），这样的数据包在网络上周期性的传输，难免对网络带宽是一种浪费。
3.WebSocket诞生
现在急需的需求是能支持客户端和服务器端的双向通信，而且协议的头部又没有HTTP的Header那么大，于是，Websocket就诞生了！
上图就是Websocket和Polling的区别，从图中可以看到Polling里面客户端发送了好多Request，而下图，只有一个Upgrade，非常简洁高效。至于消耗方面的比较就要看下图了
上图中，我们先看蓝色的柱状图，是Polling轮询消耗的流量，这次测试，HTTP请求和响应头信息开销总共包括871字节。当然每次测试不同的请求，头的开销不同。这次测试都以871字节的请求来测试。
**Use case A:**1,000 clients polling every second: Network throughput is (871 x 1,000) = 871,000 bytes = 6,968,000 bits per second (6.6 Mbps)
Use case B: 10,000 clients polling every second: Network throughput is (871 x 10,000) = 8,710,000 bytes = 69,680,000 bits per second (66 Mbps)</description></item><item><title>iOS Core Data 数据迁移 指南</title><link>https://new.halfrost.com/zh/ios_coredata_migration/</link><pubDate>Sun, 08 May 2016 08:06:00 +0000</pubDate><guid>https://new.halfrost.com/zh/ios_coredata_migration/</guid><description>####前言 Core Data是iOS上一个效率比较高的数据库框架，(但是Core Data并不是一种数据库，它底层还是利用Sqlite3来存储数据的)，它可以把数据当成对象来操作，而且开发者并不需要在乎数据在磁盘上面的存储方式。它会把位于NSManagedObject Context里面的托管对象NSManagedObject类的实例或者某个NSManagedObject子类的实例，通过NSManagedObjectModel托管对象模型，把托管对象保存到持久化存储协调器NSPersistentStoreCoordinator持有的一个或者多个持久化存储区中NSPersistentStore中。使用Core Data进行查询的语句都是经过Apple特别优化过的，所以都是效率很高的查询。
当你进行简单的设定，比如说设定某个实体的默认值，设定级联删除的操作，设定数据的验证规则，使用数据的请求模板，这些修改Core Data都会自己完成，不用自己进行数据迁移。那那些操作需要我们进行数据迁移呢？凡是会引起NSManagedObjectModel托管对象模型变化的，都最好进行数据迁移，防止用户升级应用之后就闪退。会引起NSManagedObjectModel托管对象模型变化的有以下几个操作，新增了一张表，新增了一张表里面的一个实体，新增一个实体的一个属性，把一个实体的某个属性迁移到另外一个实体的某个属性里面…………大家应该现在都知道哪些操作需要进行数据迁移了吧。
####小技巧： 进入正题之前，我先说3个调试Core Data里面调试可能你会需要的操作。
1.一般打开app沙盒里面的会有三种类型的文件，sqlite，sqlite-shm,sqlite-wal,后面2者是iOS7之后系统会默认开启一个新的“数据库日志记录模式”(database journaling mode)生成的，sqlite-shm是共享内存(Shared Memory)文件，该文件里面会包含一份sqlite-wal文件的索引，系统会自动生成shm文件，所以删除它，下次运行还会生成。sqlite-wal是预写式日志(Write-Ahead Log)文件，这个文件里面会包含尚未提交的数据库事务，所以看见有这个文件了，就代表数据库里面还有还没有处理完的事务需要提交，所以说如果有sqlite-wal文件，再去打开sqlite文件，很可能最近一次数据库操作还没有执行。
所以在调试的时候，我们需要即时的观察数据库的变化，我们就可以先禁用这个日志记录模式，只需要在建立持久化存储区的时候存入一个参数即可。具体代码如下
NSDictionary *options = @{ NSSQLitePragmasOption: @{@&amp;#34;journal_mode&amp;#34;: @&amp;#34;DELETE&amp;#34;} }; NSError *error = nil; _store = [_coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self storeURL] options:options error:&amp;amp;error]; 2.Mac上打开数据库的方式很多，我推荐3个，一个是Firefox里面直接有sqlite的插件，免费的，可以直接安装，也很方便。当然也有不用Firefox的朋友，就像我是Chrome重度使用者，那就推荐2个免费的小的app，一个是sqlitebrowser，一个是sqlite manager，这2个都比较轻量级，都比较好用。
3.如果你想看看Core Data到底底层是如何优化你的查询语句的，这里有一个方法可以看到。
先点击Product -&amp;gt;Scheme -&amp;gt;Edit Scheme
然后再切换到Arguments分页中,在Arguments Passed On Launch里面加入 “- com.apple.CoreData.SQLDebug 3”,重新运行app，下面就会显示Core Data优化过的Sql语句了。
好了，调试信息应该都可以完美显示了，可以开始愉快的进入正文了！
####一.Core Data自带的轻量级的数据迁移 这种迁移可别小看它，在你新建一张表的时候还必须加上它才行，否则会出现如下的错误：
**Failed to add store. Error: Error Domain=NSCocoaErrorDomain Code=134100 &amp;#34;(null)&amp;#34; UserInfo={metadata={** ** NSPersistenceFrameworkVersion = 641;** ** NSStoreModelVersionHashes = {** ** Item = &amp;lt;64288772 72e62096 a8a4914f 83db23c9 13718f81 4417e297 293d0267 79b04acb&amp;gt;;** ** Measurement = &amp;lt;35717f0e 32cae0d4 57325758 58ed0d11 c16563f2 567dac35 de63d5d8 47849cf7&amp;gt;;** ** };** ** NSStoreModelVersionHashesVersion = 3;** ** NSStoreModelVersionIdentifiers = (** ** &amp;#34;&amp;#34;** ** );** ** NSStoreType = SQLite;** ** NSStoreUUID = &amp;#34;9A16746E-0C61-421B-B936-412F0C904FDF&amp;#34;;** ** &amp;#34;_NSAutoVacuumLevel&amp;#34; = 2;** **}, reason=The model used to open the store is incompatible with the one used to create the store}** 错误原因写的比较清楚了，reason=The model used to open the store is incompatible with the one used to create the store，这个是因为我新建了一张表，但是我没有打开轻量级的迁移Option。这里会有人会问了，我新建表从来没有出现这个错误啊？那是因为你们用的第三方框架就已经写好了改Option了。(场外人:这年头谁还自己从0开始写Core Data啊，肯定都用第三方框架啊)那这里我就当讲解原理了哈。如果是自己从0开始写的Core Data的话，这里是应该会报错了，解决办法当然是加上代码，利用Core Data的轻量级迁移，来防止这种找不到存储区的闪退问题</description></item><item><title>永远到底有多远</title><link>https://new.halfrost.com/zh/where_is_forever/</link><pubDate>Sun, 08 May 2016 04:19:00 +0000</pubDate><guid>https://new.halfrost.com/zh/where_is_forever/</guid><description>永远有多远？­
­
闭上眼睛回忆一下，­
可曾记得几年前，几十年前你所相信所喜欢的一切？­
­
那时曾经的你也许会觉得­
周杰伦的音乐是世界上最好的音乐­
《街霸》是世界上最好玩的游戏­
金庸的武侠是世界上最好看的小说­
只要自己努力，将来必定会大展宏图成就一番事业­
这些，都是我们曾坚信的永远。­
­
我们都曾雄心勃勃，深信自己总有一天会改变这个世界­
但随着时光的流逝你却悲哀地发现­
到头来，却只是你我被世界改变­
永远，永远。­
我们经常会将这个词随口挂在嘴边，但谁知道­
永远有多远？­
你曾向往永远，张指丈量，也许就只是那么一点点­
一点，一点，一点···­
构成我们人生的，就是这密密麻麻连成一线­
名为“现在”的点。­
与其眺望远在天际的永远­
不如努力做好这一点一点­
­
只有现在，没有永远。</description></item><item><title>Remote debugging on iOS with Safari Web Inspector</title><link>https://new.halfrost.com/zh/remote_debugging_on_ios_with_safari_web_inspector/</link><pubDate>Mon, 02 May 2016 22:04:00 +0000</pubDate><guid>https://new.halfrost.com/zh/remote_debugging_on_ios_with_safari_web_inspector/</guid><description>之前在公司调试Hybrid其实很蛋疼。。都是本地打开zip包，运行js，然后调试，每次都要找到zip，比较麻烦，后来发现了这个远程调试的方法，直接插上手机就可以调试了，不用那么麻烦了，而且可以直接在手机上看到实时的效果。
后来发现有一些Js前端开发还不会这个方法，今天就分享出来，大家都看看，有啥问题请多指点。
1.首先iPhone连接上Mac，点击信任，确保itunes连接成功，然后打开iPhone的“设置” - “Safari” - &amp;ldquo;高级&amp;rdquo; - 打开“JavaScript” 和 “Web检查器”
2.打开Mac上的Safari，选择“偏好设置” - “高级” - &amp;ldquo;在菜单栏中显示“开发”菜单&amp;rdquo;
3.打开iPhone上的Safari或者运行PhoneGap程序，到某一个界面，回到Mac上的Safari上，选择“开发”，然后选择你的iPhone，就可以查看到那个一个Web界面了</description></item><item><title>iOS Hybrid 框架 —— PhoneGap</title><link>https://new.halfrost.com/zh/ios_hybrid_phonegap/</link><pubDate>Sun, 01 May 2016 21:59:00 +0000</pubDate><guid>https://new.halfrost.com/zh/ios_hybrid_phonegap/</guid><description>####前言
Hybrid App（混合模式移动应用）是指介于web-app、native-app这两者之间的app，兼具“Native App良好用户交互体验的优势”和“Web App跨平台开发的优势”。
Hybrid App按网页语言与程序语言的混合，通常分为三种类型：多View混合型，单View混合型，Web主体型，3种类型比较如下：
今天我来谈谈Web主体型中Hybrid框架里面比较有名的PhoneGap
####一.Cordova 说到PhoneGap，就不得不说到Cordova
Cordova 是一个可以让 JS 与原生代码(包括 Android 的 java，iOS 的 Objective-C 等)互相通信的一个库，并且提供了一系列的插件类，比如 JS 直接操作本地数据库的插件类。
Cordova的设计概念，是在APP上透过Web控件来呈现Web页面，让Web开发人员可以操作熟悉的语言、工具来开发APP.
为了让Web页面能够满足更多的APP功能需求，Cordova提供了Plugin机制，让Web页面能够挂载并调用Native开发技术所开发的功能模块
Cordova在系统中的层级应该是这样子的:
Cordova的层级结构是下图这样的:
Cordova又可以和Angular相结合，变成ngCordova
Cordova + Angular = ngCordova ####二.JS 与 Objective-C 通信 JS 使用了两种方式来与 Objective-C 通信，一种是使用 XMLHttpRequest 发起请求的方式，另一种则是通过设置透明的 iframe 的 src 属性。
我接下来说的主要是第二种方式，iframe bridge。
通过在 JS 端创建一个透明的 iframe，设置这个 ifame 的 src 为自定义的协议，而 ifame 的 src 更改时，UIWebView 会先回调其 delegate 的 webView:shouldStartLoadWithRequest:navigationType: 方法
说的还是很抽象的，来实际看一段代码
在cordova.js 里面，是这样子实现的
function iOSExec() { .</description></item><item><title>2016 年 3 月 iOS 面试总结</title><link>https://new.halfrost.com/zh/ios_interview/</link><pubDate>Thu, 28 Apr 2016 07:56:00 +0000</pubDate><guid>https://new.halfrost.com/zh/ios_interview/</guid><description>今年3月中下旬因为个人原因，换了一份工作，期间面试了有4，5家，基本都是D轮或者上市公司，也从他们的面试笔试中看到了自己的一些不足，于是就想写出来和大家分享一下，如果能帮到正在面试的同学更好。从面试题中，其实可以看到一些行业的发展，以及总体人才需求是怎样的了。
####一.笔试题 笔试基本都有一两道基础题，比如说UITableView的重用机制，ARC的基本原理，如何避免retain cycle，谈谈对MVC的理解，iOS内存管理机制。这些大家应该都很清楚了。笔试的内容有几种有选择题，问答题，难一点的就是多选题了。我面试了一家就是给了10道多选题，多选，少选，错选都不行，当时做完以后就感觉不是很好，有些题目题干就是一下哪些是对的，然后ABCD依次给4个不同的概念，这种一道题相当于考了4个点。总之遇到这种“恶心”的多选题也不要太慌，静下心来一一甄别应该能拿到不错的成绩。
接下来我说几个我当时答的不怎么好的题目，我当时记了一下，和大家分享一下。
#####1.进程和线程的区别和联系 这个其实是操作系统的问题，当时一下子把我问的懵了，后来仔细回想了一下，加上自己的理解就答了，下面说说稍微完整的答案，大家可以准备准备，再问这种问题就可以完美作答了。
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.  一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.
#####2.并行和并发的区别
并行是指两个或者多个事件在同一时刻发生； 并发是指两个或多个事件在同一时间间隔内发生。
#####3.谈谈你对Block和delegate的理解 我当时是这么答的，delegate的回调更多的面向过程，而block则是面向结果的。如果你需要得到一条多步进程的通知，你应该使用delegation。而当你只是希望得到你请求的信息（或者获取信息时的错误提示），你应该使用block。（如果你结合之前的3个结论，你会发现delegate可以在所有事件中维持state，而多个独立的block却不能）
#####4.谈谈instancetype和id的异同 1、相同点：都可以作为方法的返回类型
2、不同点： ①instancetype可以返回和方法所在类相同类型的对象，id只能返回未知类型的对象；
②instancetype只能作为返回值，不能像id那样作为参数
#####5.category中能不能使用声明属性？为什么？如果能，怎么实现？ 这种问题一问，我当时就感觉肯定能实现的，但是实在不知道怎么做，后来回来查了一下，才知道是用到了Runtime的知识了。贴一下答案
给分类（Category）添加属性
利用Runtime实现getter/setter 方法
@interface ClassName (CategoryName) @property (nonatomic, strong) NSString *str; @end //实现文件 #import &amp;#34;ClassName + CategoryName.h&amp;#34; #import &amp;lt;objc/runtime.h&amp;gt; static void *strKey = &amp;amp;strKey; @implementation ClassName (CategoryName) -(void)setStr:(NSString *)str { objc_setAssociatedObject(self, &amp;amp; strKey, str, OBJC_ASSOCIATION_COPY); } -(NSString *)str { return objc_getAssociatedObject(self, &amp;amp;strKey); } @end #####6.</description></item><item><title>搭建 Phabricator 我遇到的那些坑</title><link>https://new.halfrost.com/zh/phabricator_trouble/</link><pubDate>Mon, 25 Apr 2016 01:54:00 +0000</pubDate><guid>https://new.halfrost.com/zh/phabricator_trouble/</guid><description>一.可能会用到的命令 1.重启phd守护线程 先进入到Fabricator文件夹下面，然后 $./bin/phd/ log
2.删除一个代码仓库 $ ./bin/remove destroy rMOBILE(代码库的前缀名字)
3.重启mysql数据库
sudo launchctl unload -F /Library/LaunchDaemons/com.oracle.oss.mysql.mysqld.plistsudo launchctl load -F /Library/LaunchDaemons/com.oracle.oss.mysql.mysqld.plist4.重启apache服务器命令
sudo /usr/sbin/apachectl restartsudo apachectl -k restart二.可能会用到的一些坑 1.Mac如果出现“sudo: /etc/sudoers is owned by uid 501, should be 0 ”问题解决办法 先启用root账户 在 OS X 中启用和使用“root”用户 OS X Lion (10.7) 和更高版本
从 Apple 菜单中选取“系统偏好设置”。 从“显示”菜单中选取“用户与群组”。 点按锁图标并使用管理员帐户进行鉴定。 点按“登录选项”。 点按右下方的“编辑”或“加入”按钮。 点按“打开目录实用工具”按钮。 点按“目录实用工具”窗口中的锁图标。 输入管理员帐户名称和密码，然后点按“好”。 从“编辑”菜单中选取“启用 Root 用户”。 在“密码”和“验证”栏中输入您想要使用的 root 密码，然后点按“好”。 Mac OS X Snow Leopard (10.</description></item><item><title>Code review - Phabricator Use guide introduce</title><link>https://new.halfrost.com/zh/code_review_phabricator_use_guide_introduce/</link><pubDate>Sun, 24 Apr 2016 01:27:00 +0000</pubDate><guid>https://new.halfrost.com/zh/code_review_phabricator_use_guide_introduce/</guid><description>前言 今天给大家分享一下我之前在公司搭建的一个Code Review服务器的一些心得吧。由于现在移动互联网更新迭代速度很快，分布版本的速度基本上决定了创业公司的生命，所以代码质量在决定产品质量上也体现出尤其重要的地位。
目录 1.Phabricator Summary 2.pre-push code review tool —— Differential 3.code repository browse tool — Diffusion 4.post-push code review tool —— Audit 5.Other Feature Summary 6.Final 一.Phabricator Summary 今天我要向大家分享的是一款非常棒的代码检视工具Phabricator。Phabricator是Facebook保驾护航的11大IT技术之一。在Phabricator的网站中，开发者给出了这样的描述：“Facebook的工程师们毫不掩饰自己对于Phabricator的喜爱之情，他们甚至将它视为’顺利’与’严谨’的代名词”。下面我就将演示使用Phabricator进行代码检视的流程以及它的亮点。
Facebook 保价护航的11大IT技术
1.HTML5
2.Facebook平台
3.Facebook虚拟币
4.Facebook应用
5.开放计算项目
6.Hadoop
7.LAMP堆栈
8.Scuba
9.HipHop For PHP
10.Scribe 与 Thift
11.Phabricator
这就是搭建好的服务器的界面
二.Differential Differential是Phabricator核心功能之一，它是开发者相互检视代码，互相讨论代码的主要平台。
谈到如何生成Diff，此处需要用到Arcanist Tool工具了。
1.DownLoad Tool 下载Arcanist Tool
2.Edit Path 配置path路径
3.install certificate 安装证书
4.install certificate 验证证书token
5.creat diff 生成diff</description></item><item><title>欢迎使用 Ghost 博客系统</title><link>https://new.halfrost.com/zh/welcome-to-ghost/</link><pubDate>Wed, 29 Jul 2015 16:43:00 +0000</pubDate><guid>https://new.halfrost.com/zh/welcome-to-ghost/</guid><description>Yeah，博客上线了！这篇文章的目的是向你介绍 Ghost 编辑器并帮你快速上手。通过 &amp;lt;your blog URL&amp;gt;/ghost/ 链接就可以登录系统后台管理你的博客内容了。当你进入后台，你就能看到左侧文章列表处列出的这篇文章，右侧就是这篇文章的预览效果。点击预览栏右上角的铅笔图标就能进入内容编辑页面。
快速入门 Ghost 使用 Markdown 语法书写内容。简单来说，Markdown 就是一种简化的书写格式！
用 Markdown 语法写作是很容易的。在编辑界面的左侧就是你写作的地方。在你认为需要的时候，可以使用以下这些语法来格式化你的内容。例如下面这个无序列表：
Item number one Item number two A nested item A final item 还可以是有序列表：
Remember to buy some milk Drink the milk Tweet that I remembered to buy the milk, and drank it 链接 如果要链接其它页面，可以直接把页面的 URL 粘贴过来，例如 http://www.ghostchina.com - 会被自动识别为链接。但是，如果你想自定义链接文本，可以像这样： Ghost 中文网。很简单吧！
图片 插入图片也没问题！前提是你事先知道图片的 URL，然后像下面这样：
如果图片在本地的硬盘里怎么办？也很简单！像下面这样书写就能为图片预留一个位置，然后你可以继续写作，回头再通过拖拽的方式把图片上传到服务器上。
![一张图片]
引用 有些时候我们需要引用别人说的话，可以这样：</description></item></channel></rss>