<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>loveltyoic (loveltyoic)</title>
    <link>https://ruby-china.org/loveltyoic</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>用过七牛的来说说你们的安全策略吧</title>
      <description>&lt;h2 id="先说下我的使用场景："&gt;先说下我的使用场景：&lt;/h2&gt;
&lt;p&gt;上传方式是七牛直传：用户在 APP 端用七牛的 SDK 直接传图片到七牛服务器，然后七牛回调我服务器上的接口。&lt;/p&gt;

&lt;p&gt;客户端上传首先需要一个 token，这个 token 当然是由自己的服务器颁发，那么就涉及到一个安全问题，如何限制恶意用户窃取 token 并上传垃圾文件？（七牛是可以限制上传文件类型和大小的，这个问题就可以不考虑了。）&lt;/p&gt;
&lt;h2 id="我的想法是从两方面来解决这个问题："&gt;我的想法是从两方面来解决这个问题：&lt;/h2&gt;&lt;h3 id="限制token获取"&gt;限制 token 获取&lt;/h3&gt;
&lt;p&gt;因为 token 本身也是通过 API 来获取的，那么如何区分恶意用户和正常请求呢？这其实是一个普遍的问题，就是如何保护 API。我觉得这里存在一个矛盾：&lt;strong&gt;API 本身是开放的，但是我们需要限制一些 API 的使用&lt;/strong&gt;，如何做到？欢迎分享下这方面的经验。&lt;/p&gt;

&lt;p&gt;回到这个问题。如果上传限制为登录用户才能使用的话，可以在 token 获取阶段附上用户 id，然后硬性限制每个 id 每天获取 token 的次数，比如 50 次。但是问题又出现了，如果恶意用户把参数篡改成其他的用户 id，限制就失去意义了。为了防止篡改请求，第一个想到的就是对请求进行签名。首先服务器和客户端需要共享一个秘密字符串&lt;code&gt;secret&lt;/code&gt;，这需要在客户端 hardcode。&lt;/p&gt;

&lt;p&gt;算法采用 sha1 版的 hmac，Ruby 里面就是&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlsafe_encode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;OpenSSL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HMAC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;OpenSSL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Digest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sha1'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"uid=xxx"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;摘要算法是单向的，攻击者拿到这个摘要字符串也无法逆向出 secret。服务端在收到请求后做同样的计算来验证有效性。但这个方法的前提是攻击者无法获取到 secret。可是如果客户端被逆向工程破解了呢？由于 secret 是 hardcode 在客户端的，如果攻击者看到客户端源码，secret 就暴露了。
对这方面还不了解，欢迎科普。&lt;/p&gt;

&lt;p&gt;另外 token 是有时效的，默认为 1 小时。那么在这个有效期内，这个 token 就可以无限制上传了，所以有效期也不能这么久。&lt;/p&gt;
&lt;h3 id="限制token使用"&gt;限制 token 使用&lt;/h3&gt;
&lt;p&gt;通过有效期来限制，感觉并不是完美的解决方法，到底设置多久也是个问题，太短的话可能影响到正常操作，太长的话又失去了意义。&lt;/p&gt;

&lt;p&gt;限制 token 使用的话最好能做到一次性，用完即失效。
  目前知道的方式是在生成 token 的时候通过 scope 来指定文件的 key，另外设置 insertOnly 为 1，也就是服务端硬性规定好保存的文件名，重名的话上传是无效的。
  这样一个 token 就只能上传一个文件。&lt;/p&gt;

&lt;p&gt;以上就是我能想到的方法了，希望大家踊跃分享自己的使用经验！&lt;/p&gt;</description>
      <author>loveltyoic</author>
      <pubDate>Tue, 28 Jul 2015 10:57:28 +0800</pubDate>
      <link>https://ruby-china.org/topics/26671</link>
      <guid>https://ruby-china.org/topics/26671</guid>
    </item>
    <item>
      <title>em-synchrony 中连接池的实现原理</title>
      <description>&lt;p&gt;最近在恶补并发的知识，看了不少代码和博客，稍微有了一点点感觉。趁着印象还比较深刻，先记下来目前对 eventmachine 和 fiber 使用的理解。&lt;/p&gt;

&lt;p&gt;通过读 em-synchrony 的代码，发现其中连接池的实现是一个很好的例子，下面就通过模仿 em-synchrony 线程池的实现来说明我所理解的使用模式。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'fiber'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'eventmachine'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Connection&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Pool&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 构造连接的数组，这里用虚构的Connection代替，可以抽象的理解为各种资源，如mysql连接，redis连接...&lt;/span&gt;
    &lt;span class="vi"&gt;@connections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;# 等待队列，存放等待资源的fiber&lt;/span&gt;
    &lt;span class="vi"&gt;@pending&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;
    &lt;span class="c1"&gt;# 获取资源，当没有空闲资源的时候，会让出执行，而不会阻塞进程&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;acquire&lt;/span&gt;
    &lt;span class="c1"&gt;# 只有在拿到资源后才会执行到这里&lt;/span&gt;
    &lt;span class="s1"&gt;'get conn, do job'&lt;/span&gt;
    &lt;span class="c1"&gt;# 传过来的block应该是一个异步的任务&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;block_given?&lt;/span&gt;
    &lt;span class="n"&gt;release&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;acquire&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;
    &lt;span class="c1"&gt;# 试图去连接池中拿&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@connections.pop&lt;/span&gt;
    &lt;span class="c1"&gt;# 拿到了就直接返回&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;
    &lt;span class="c1"&gt;# 如果没拿到，就需要等待&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'waiting for connection'&lt;/span&gt;
    &lt;span class="c1"&gt;# 但是这里的等待并不是阻塞等待的，因为有了fiber，我们可以在这里让出执行权&lt;/span&gt;
    &lt;span class="c1"&gt;# 只要将当前的fiber加入到等待队列中&lt;/span&gt;
    &lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;yield&lt;/span&gt; &lt;span class="vi"&gt;@pending.push&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;
    &lt;span class="c1"&gt;# 当fiber被唤醒时，会在这里继续执行，再次尝试acquire&lt;/span&gt;
    &lt;span class="n"&gt;acquire&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;release&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 把连接放回连接池&lt;/span&gt;
    &lt;span class="vi"&gt;@connections.push&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;
    &lt;span class="c1"&gt;# 当资源被释放时，会从等待队列中唤醒一个fiber&lt;/span&gt;
    &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@pending.shift&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;怎么用？&lt;/em&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;EM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; 
  &lt;span class="c1"&gt;# 容量为3的资源池&lt;/span&gt;
  &lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# 其他事件&lt;/span&gt;
  &lt;span class="no"&gt;EM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_periodic_timer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'concurrent running!'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# 执行10件任务，10个fiber协作&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# 这里注册了事件后就会继续向下执行&lt;/span&gt;
        &lt;span class="c1"&gt;# 相当于模拟了一个需要执行time时间的异步任务&lt;/span&gt;
        &lt;span class="c1"&gt;# 任务的执行不会阻塞进程，而是在任务完成后的callback中才唤醒自身&lt;/span&gt;
        &lt;span class="no"&gt;EM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_timer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; 
          &lt;span class="c1"&gt;# 在callback中恢复执行&lt;/span&gt;
          &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"mission complete in &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="c1"&gt;# 立刻让出执行权&lt;/span&gt;
        &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;yield&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以执行上面的代码，通过输出可以对整个过程有更好的理解。&lt;/p&gt;

&lt;p&gt;这种基于 fiber 的连接池一定要配合 eventmachine 一起用才能发挥作用，其实就是 em-synchrony 的原理。em-synchrony 将整个块放到 fiber 中执行，注册事件后就&lt;code&gt;Fiber.yield&lt;/code&gt;让权，并在 callback 中唤醒。这样通过包装将 callback 隐藏起来了。
节选一段 em-http 的实现，可以看出相同的实现模式：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;EventMachine&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;HTTPMethods&lt;/span&gt;
     &lt;span class="sx"&gt;%w[get head post delete put patch options]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
       &lt;span class="nb"&gt;class_eval&lt;/span&gt; &lt;span class="sx"&gt;%[
         alias :a&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt; :&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;
         def &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;(options = {}, &amp;amp;blk)
           f = Fiber.current

           conn = setup_request(:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;, options, &amp;amp;blk)
           if conn.error.nil?
             conn.callback { f.resume(conn) }
             conn.errback  { f.resume(conn) }

             Fiber.yield
           else
             conn
           end
         end
      ]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为什么事件驱动很重要？因为如果在拿到资源后同步执行，那么等待返回的过程会阻塞整个进程，也就是说 fiber 只能一个接一个的执行，那么就退化到了跟不用 fiber 一样，都是顺序执行了。&lt;/p&gt;

&lt;p&gt;因为使用 eventmachine，或者说 reactor 模式。每个 fiber 在拿到连接后其实只需要注册一个事件，然后就把自己挂起（让出执行权），在事件完成后会执行 callback，并在 callback 中唤醒之前的任务，并赋给它异步请求的结果。&lt;/p&gt;

&lt;p&gt;而没拿到连接的 fiber 会让出执行权，在其他 fiber 释放了连接后才被唤醒。因为大家都是事件驱动，不会因为 IO 或是等待连接而阻塞，从而让所有任务可以并发的执行。&lt;/p&gt;

&lt;p&gt;以上就是我的理解，欢迎指正与讨论。&lt;/p&gt;</description>
      <author>loveltyoic</author>
      <pubDate>Fri, 03 Apr 2015 16:46:17 +0800</pubDate>
      <link>https://ruby-china.org/topics/24984</link>
      <guid>https://ruby-china.org/topics/24984</guid>
    </item>
    <item>
      <title>[转王垠] 怎样尊重一个程序员</title>
      <description>&lt;p&gt;首先说明，这是王垠的一篇文章，这里只是贴个链接。
不知道的可以了解一下这个人。
&lt;a href="http://www.yinwang.org/blog-cn/2015/03/03/how-to-respect-a-programmer/" rel="nofollow" target="_blank"&gt;http://www.yinwang.org/blog-cn/2015/03/03/how-to-respect-a-programmer/&lt;/a&gt;&lt;/p&gt;</description>
      <author>loveltyoic</author>
      <pubDate>Fri, 13 Mar 2015 08:40:09 +0800</pubDate>
      <link>https://ruby-china.org/topics/24627</link>
      <guid>https://ruby-china.org/topics/24627</guid>
    </item>
    <item>
      <title>在 Swift 中应用 Grand Central Dispatch (上)</title>
      <description>&lt;p&gt;本文译自&lt;a href="http://www.raywenderlich.com/79149/grand-central-dispatch-tutorial-swift-part-1" rel="nofollow" target="_blank" title=""&gt;Grand Central Dispatch Tutorial for Swift: Part 1/2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;尽管 &lt;strong&gt;Grand Central Dispatch&lt;/strong&gt; (GCD) 已经存在一段时间了，但并非每个人都知道怎么使用它。这是情有可原的，因为并发很棘手，而且 GCD 本身基于 C 的 API 在 Swift 世界中很刺眼。
在这两篇教程中，你会学到 GCD 的来龙去脉。第一部分解释了 GCD 可以做什么和几个基本功能。第二部分，你会学到一些 GCD 所提供的进阶功能。&lt;/p&gt;


&lt;h2 id="起步"&gt;起步&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;libdispatch&lt;/em&gt;是 Apple 所提供的在 IOS 和 OS X 上进行并发编程的库，而 GCD 正是它市场化的名字。GCD 有如下优点：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GCD 可以将计算复杂的任务放到后台执行，从而提升 app 的响应性能&lt;/li&gt;
&lt;li&gt;GCD 提供了比锁和线程更简单的并发模型，帮助开发者避免并发的 bug。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;为了理解 GCD，你需要了解一些线程和并发的概念。这些概念可能很含糊并且细微，所以先简要回顾一下。&lt;/p&gt;
&lt;h3 id="串行 vs. 并发"&gt;串行 vs. 并发&lt;/h3&gt;
&lt;p&gt;这两个词用来描述任务的执行顺序。 &lt;strong&gt;串行&lt;/strong&gt; 在同一时间点总是单独执行一个任务，而并发可以同时执行多个任务。&lt;/p&gt;
&lt;h3 id="任务"&gt;任务&lt;/h3&gt;
&lt;p&gt;在本教程中，你可以把任务当做一个闭包 (closure)。实际上，你可以将 GCD 和函数指针一起使用，但是一般很少这样使用。闭包更简单！&lt;/p&gt;

&lt;p&gt;不记得 Swift 中的闭包？闭包是自含的，可保存传递并被调用的代码块。当调用的时候，他们的用法很像函数，可以有参数和返回值。除此之外，闭包可以“捕获”外部的变量，也就是说，它可以看到并记住它自身被定义时的作用域变量。&lt;/p&gt;

&lt;p&gt;Swift 中的闭包和 OC 中的块 (block) 类似甚至于他们几乎就是可交换使用的。唯一的限制在于 OC 中不能使用 Swift 独有的特性，比如元组 (tuple)。但 OC 中的块可以安全的替换成 Swift 中的闭包。&lt;/p&gt;
&lt;h3 id="同步 vs. 异步"&gt;同步 vs. 异步&lt;/h3&gt;
&lt;p&gt;这两个词描述的是函数何时将控制权返回给调用者，以及在返回时任务的完成情况。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;同步&lt;/em&gt;函数只有在任务完成后才会返回。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;异步&lt;/em&gt;函数会立即返回，不会等待任务完成。因此异步函数不会阻塞当前线程。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;   -- 当你读到同步函数阻塞 (block) 当前进程或者函数是阻塞 (blocking) 函数时，不要困惑！动词阻塞（block）描述的是函数对当前线程的影响，和块 (block) 没有关系。同时记住 GCD 文档中有关 OC 的 block 可以跟 Swift 的闭包互换。&lt;/p&gt;
&lt;h3 id="临界区（Critical Section）"&gt;临界区（Critical Section）&lt;/h3&gt;
&lt;p&gt;这是一段不能并发执行的代码，也就是说两个线程不可以同时执行它。这通常是因为这段代码会修改共享的资源。否则，并发的进程同时修改同一个变量会导致错误。&lt;/p&gt;
&lt;h3 id="竞态条件"&gt;竞态条件&lt;/h3&gt;
&lt;p&gt;当两个线程竞争同一资源时，如果对资源的访问顺序敏感，就称存在竞态条件。竞态条件可能产生在代码检查时不易被发现的不可预期行为。&lt;/p&gt;
&lt;h3 id="死锁"&gt;死锁&lt;/h3&gt;
&lt;p&gt;两个或更多的线程因等待彼此完成而陷入的困境称为死锁。第一个线程无法完成因为它在等待第二个线程完成。但是第二个线程也无法完成因为它在等待第一个线程完成。&lt;/p&gt;
&lt;h3 id="线程安全"&gt;线程安全&lt;/h3&gt;
&lt;p&gt;线程安全的代码是可以被多个线程或并发任务安全调用的，他不会造成任何问题（数据错误，崩溃等）。非线程安全的代码在同一时间只能单独执行。一段线程安全的代码如&lt;code&gt;let a = ["thread-safe"]&lt;/code&gt;。由于数组是只读的，它可以被多个线程同时使用而不会引发问题。另一方面，&lt;code&gt;var a = ["thread-unsafe"]&lt;/code&gt;是可变数组。这意味着它不是线程安全的，因为多个线程可以同时获取并修改这个数组，会得到不可预料的结果。非线程安全的变量和可变的数据结构在同一时刻应该只能被一个线程获取。&lt;/p&gt;
&lt;h3 id="上下文切换"&gt;上下文切换&lt;/h3&gt;
&lt;p&gt;上下文切换是在进程中切换不同线程时保存和恢复程序执行状态的过程。这一过程在编写多任务 app 时相当常见，但是会造成一些额外开支。&lt;/p&gt;
&lt;h3 id="并发 vs 并行"&gt;并发 vs 并行&lt;/h3&gt;
&lt;p&gt;并发和并行经常会被同时提起，所以值得通过简短的解释来区分彼此。&lt;/p&gt;

&lt;p&gt;并发代码中的单独部分可以同时执行。然而，这要由系统来决定并发怎样发生或是否发生。&lt;/p&gt;

&lt;p&gt;多核设备通过并行来同时执行多个线程；然而，在单核设备中，必须要通过上下文切换来运行另一个线程或进程。这一过程通常发生的很快以至于给人并行的假象。如下图所示
&lt;img src="http://cdn1.raywenderlich.com/wp-content/uploads/2014/01/Concurrency_vs_Parallelism.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;尽管你可能在 GCD 之下编写并发执行的代码，但仍由 GCD 来决定并行的需求有多大。&lt;/p&gt;

&lt;p&gt;深层次的观点是并发实际上是关乎&lt;em&gt;结构&lt;/em&gt;的。当你编写 GCD 代码时，你组织你的代码来揭示出可以同时运行的工作，以及不可以同时运行的。如果你想深入了解这个主题，猛击&lt;a href="http://vimeo.com/49718712" rel="nofollow" target="_blank" title=""&gt;Rob Pike&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id="队列"&gt;队列&lt;/h2&gt;
&lt;p&gt;GCD 提供了 &lt;strong&gt;调度队列&lt;/strong&gt; （dispatch queues）来处理提交的任务；这些队列管理着你向 GCD 提交的任务并且以先进先出（FIFO）的顺序来执行任务。这保证了第一个加入队列的任务第一个被执行，第二个加入的任务第二个开始执行，以此类推。&lt;/p&gt;

&lt;p&gt;所有调度队列都是线程安全的从而让你可以同时在多个线程中使用它们。当你明白了调度队列如何为你的代码提供了线程安全性时，GCD 的优点就很明显了。关键是选择正确的调度队列种类和正确的 &lt;strong&gt;调度函数&lt;/strong&gt; （dispatching function）来提交你的任务。&lt;/p&gt;
&lt;h3 id="顺序队列"&gt;顺序队列&lt;/h3&gt;
&lt;p&gt;顺序队列中的任务同一时间只执行一件任务，每件任务只有在先前的任务完成后才开始。同时，你并不知道一个任务完成到另一个任务开始之间的间隔时间，如下图所示：
&lt;img src="http://cdn2.raywenderlich.com/wp-content/uploads/2014/09/Serial-Queue-Swift.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;任务的执行是在 GCD 掌控之下的；你唯一确定的就是 GCD 在同一时刻只执行一件任务并且按任务加入队列的顺序执行。&lt;/p&gt;

&lt;p&gt;因为不会在顺序队列中同时执行两件任务，所以没有多个任务同时进入临界区的危险；这保证了临界区不会出现竞态条件。因此如果进入临界区的唯一途径就是通过向调度队列提交任务，那么可以保证临界区是安全的。&lt;/p&gt;
&lt;h3 id="并发队列"&gt;并发队列&lt;/h3&gt;
&lt;p&gt;并发队列中的任务可以保证按进入队列的顺序被执行...仅此而已！任务可能以任意顺序完成而且你不知道何时下一个任务会开始，或是任一时刻有多少任务在运行。再一次，这完全取决于 GCD。
下图展示了四个并发任务的例子:
&lt;img src="http://cdn2.raywenderlich.com/wp-content/uploads/2014/09/Concurrent-Queue-Swift.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;任务 1，2 和 3 都运行的很快，一个接一个。但是任务 1 在任务 0 开始了一段时间后才开始。同时，任务 3 在任务 2 开始后才开始但是却更早完成。&lt;/p&gt;

&lt;p&gt;何时开始一个任务完全取决于 GCD。如果一个任务的执行时间和另一个的发生重叠，将由 GCD 来决定是否要将任务运行在另一个可用的核上或是通过上下文切换来运行另一个程序。&lt;/p&gt;

&lt;p&gt;有趣的是，GCD 为每种队列类型提供了至少&lt;em&gt;5&lt;/em&gt;种特别的队列。&lt;/p&gt;
&lt;h3 id="队列类型"&gt;队列类型&lt;/h3&gt;
&lt;p&gt;首先，系统提供了一种特殊的顺序队列 &lt;strong&gt;main queue&lt;/strong&gt;。和其他的顺序队列一样，在这个队列里的任务同一时刻只有一个在执行。然而，这个队列保证了所有任务会在主线程中执行，主线程是唯一一个允许更新 UI 的线程。这个队列用来向 &lt;strong&gt;UIView&lt;/strong&gt;  对象发消息或发通知。&lt;/p&gt;

&lt;p&gt;系统同时提供了几种并发队列。这些队列和它们自身的 QoS 等级相关。QoS 等级表示了提交任务的意图，使得 GCD 可以决定如何制定优先级。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;QOS_CLASS_USER_INTERACTIVE：  &lt;strong&gt;user interactive&lt;/strong&gt; 等级表示任务需要被立即执行以提供好的用户体验。使用它来更新 UI，响应事件以及需要低延时的小工作量任务。这个等级的工作总量应该保持较小规模。&lt;/li&gt;
&lt;li&gt;QOS_CLASS_USER_INITIATED： &lt;strong&gt;user initiated&lt;/strong&gt; 等级表示任务由 UI 发起并且可以异步执行。它应该用在用户需要即时的结果同时又要求可以继续交互的任务。&lt;/li&gt;
&lt;li&gt;QOS_CLASS_UTILITY： &lt;strong&gt;utility&lt;/strong&gt;  等级表示需要长时间运行的任务，常常伴随有用户可见的进度指示器。使用它来做计算，I/O，网络，持续的数据填充等任务。这个等级被设计成节能的。&lt;/li&gt;
&lt;li&gt;QOS_CLASS_BACKGROUND： &lt;strong&gt;background&lt;/strong&gt; 等级表示那些用户不会察觉的任务。使用它来执行预加载，维护或是其它不需用户交互和对时间不敏感的任务。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;要清楚 Apple 的 API 同时也使用了全局调度队列（global dispatch queue），所以你添加的任何任务都不是这些队列中的唯一任务。&lt;/p&gt;

&lt;p&gt;最后，你可以创建自定义的顺序或并发队列。意味着你至少有&lt;em&gt;5&lt;/em&gt;种队列：主队列（main queue），四种通用调度队列，加上任意你自己定制的队列！&lt;/p&gt;

&lt;p&gt;以上就是调度队列的主要部分！&lt;/p&gt;

&lt;p&gt;GCD 的“艺术”可归结为选择正确的队列调度函数来提交任务。最佳的学习方式就是通过下面的例子。&lt;/p&gt;
&lt;h2 id="示例"&gt;示例&lt;/h2&gt;
&lt;p&gt;因为这篇教程的目标是使用 GCD 优化程序以及在不同线程中安全的运行代码，所以你会以一个几近完成的项目 GooglyPuff 来开始。&lt;/p&gt;

&lt;p&gt;GooglyPuff 是一个未优化，非线程安全的 app，使用 Core Image 的人脸识别 API 在人脸上叠加金鱼眼。初始图像可以从图片库中选择或是从网络下载一组预定的图片。&lt;/p&gt;

&lt;p&gt;&lt;a href="http://cdn5.raywenderlich.com/wp-content/uploads/2015/01/GooglyPuff_Swift_Start_1_Xcode61.zip" rel="nofollow" target="_blank" title=""&gt;GooglyPuff_Swift_Start_1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;一旦下载了工程，提取到合适的地方，打开 Xcode 并运行它。看起来如下：
&lt;img src="http://cdn3.raywenderlich.com/wp-content/uploads/2014/01/Workflow1.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;注意到当你选择 &lt;strong&gt;Le Internet&lt;/strong&gt; 选项来下载图片时，一个&lt;code&gt;UIAlertController&lt;/code&gt;提示框会过早的弹出。你会在教程的第二部分修复这个问题。&lt;/p&gt;

&lt;p&gt;这个工程中有 4 个需要关心的类：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PhotoCollectionViewController&lt;/code&gt;：app 启动后的第一个视图控制器。展示所有选择的图片的缩略图。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PhotoDetailViewController&lt;/code&gt;：为图片加上金鱼眼并在&lt;code&gt;UIScrollView&lt;/code&gt;中展示。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Photo&lt;/code&gt;：描述图片属性的协议。提供图片，缩略图和状态。两个类实现了这个协议：&lt;code&gt;DownloadPhoto&lt;/code&gt;从&lt;code&gt;NSURL&lt;/code&gt;实例化图片，&lt;code&gt;AssetPhoto&lt;/code&gt;从&lt;code&gt;ALAsset&lt;/code&gt;实例化图片。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PhotoManager&lt;/code&gt;：管理所有&lt;code&gt;Photo&lt;/code&gt;对象。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="使用dispatch_sync处理后台任务"&gt;使用&lt;code&gt;dispatch_sync&lt;/code&gt;处理后台任务&lt;/h2&gt;
&lt;p&gt;返回 app 并从图片库中添加一些图片或使用 &lt;strong&gt;Le Internet&lt;/strong&gt; 选项下载一些。&lt;/p&gt;

&lt;p&gt;留意在轻触&lt;code&gt;PhotoCollectionViewController&lt;/code&gt;中的&lt;code&gt;UICollectionViewCell&lt;/code&gt;后要多久才能完成&lt;code&gt;PhotoDetailViewController&lt;/code&gt;的初始化；此时存在明显的延迟，尤其是在较慢的设备上浏览较大的图片时。&lt;/p&gt;

&lt;p&gt;一不小心就会在&lt;code&gt;UIViewController&lt;/code&gt;的&lt;code&gt;viewDidLoad&lt;/code&gt;中填充过多杂乱的方法而造成超负荷；以至于经常要等待很久视图控制器才会出现。如果可能的话，最好将一些工作转移到后台去完成，如果这些工作在加载时不是必需的。&lt;/p&gt;

&lt;p&gt;听起来是使用&lt;code&gt;dispatch_async&lt;/code&gt;的时候！&lt;/p&gt;

&lt;p&gt;打开&lt;code&gt;PhotoDetailViewController&lt;/code&gt;然后用下面的实现替换&lt;code&gt;viewDidload&lt;/code&gt;：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;override func viewDidLoad() {
  super.viewDidLoad()
  assert(image != nil, "Image not set; required to use view controller")
  photoImageView.image = image

  // Resize if neccessary to ensure it's not pixelated
  if image.size.height &amp;lt;= photoImageView.bounds.size.height &amp;amp;&amp;amp;
     image.size.width &amp;lt;= photoImageView.bounds.size.width {
    photoImageView.contentMode = .Center
  }

  dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)) { // 1
    let overlayImage = self.faceOverlayImageFromImage(self.image)
    dispatch_async(dispatch_get_main_queue()) { // 2
      self.fadeInNewImage(overlayImage) // 3
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面代码的工作流程：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;首先将工作从主线程上转移到全局队列中。因为这是一个&lt;code&gt;dispatch_async&lt;/code&gt;调用，异步提交的闭包意味着调用线程会继续执行下去。这使得&lt;code&gt;viewDidLoad&lt;/code&gt;在主线程上更早的完成从而让加载的过程在感觉上更迅速。同时，人脸识别过程已经开始并会在晚些时候完成。&lt;/li&gt;
&lt;li&gt;在这时，人脸识别已经完成并生成一张新图片。因为要用这张新图片更新&lt;code&gt;UIImageView&lt;/code&gt;，所以把一个闭包加入主线程中。记住 -- 必须总是在主线程中操作&lt;code&gt;UIKit&lt;/code&gt;！&lt;/li&gt;
&lt;li&gt;最后，用&lt;code&gt;fadeInNewImage&lt;/code&gt;更新 UI。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;注意到你在使用 Swift 的尾随闭包（trailing closure）语法，将闭包写在参数括号的后面传给&lt;code&gt;dispatch_async&lt;/code&gt;。这种语法看起来更清晰，因为闭包没有内嵌到函数括号中。&lt;/p&gt;

&lt;p&gt;运行 app；选择一张图片然后你会明显地发现视图控制器载入更快了，随后金鱼眼会加入进来。这给 app 带来了很好的效果，因为你展示出图片修改前后的变化。同时，如果你试图加载一张极其巨大的图片，app 不会因为加载视图控制器而失去响应，这让 app 有很好的适应性。&lt;/p&gt;

&lt;p&gt;正如前面所提到的，&lt;code&gt;dispatch_async&lt;/code&gt;以闭包的形式向队列中追加了一项任务并立即返回了。这项任务会在 GCD 决定的稍后时间执行。当你需要执行网络请求或在后台执行繁重的 CPU 任务时，使用&lt;code&gt;dispatch_async&lt;/code&gt;不会阻塞当前进程。&lt;/p&gt;

&lt;p&gt;何时使用何种队列类型快速指南：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;自定义顺序队列：当你想顺序执行后台任务并追踪它时，这是一个很好的选择。因为同时只有一个任务在执行，因此消除了资源竞争。注意如果需要从方法中获取数据，你必须内置另一个闭包来得到它或者考虑使用&lt;code&gt;dispatch_sync&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;主队列（顺序）：当并发队列中的任务完成需要更新 UI 的时候，这是一个通常的选择。为达此目的，需要在一个闭包中嵌入另一个闭包。同时，如果在主队列中调用&lt;code&gt;dispatch_async&lt;/code&gt;来返回主队列，能保证新的任务会在当前方法完成后再执行。&lt;/li&gt;
&lt;li&gt;并发队列：通常用来执行与 UI 无关的后台任务。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="获取全局队列的帮助变量（Helper Variable）"&gt;获取全局队列的帮助变量（Helper Variable）&lt;/h2&gt;
&lt;p&gt;你可能注意到&lt;code&gt;dispatch_get_global_queue&lt;/code&gt;的 QoS 等级参数写起来有些繁琐。这是由于&lt;code&gt;qos_class_t&lt;/code&gt;被定义为一个结构体，它包含有&lt;code&gt;Uint32&lt;/code&gt;型的属性&lt;code&gt;value&lt;/code&gt;，而这个属性需要被转型为&lt;code&gt;Int&lt;/code&gt;。在 &lt;strong&gt;Utils.swift&lt;/strong&gt; 中添加一些全局的计算变量，使获取全局队列更方便一些：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var GlobalMainQueue: dispatch_queue_t {
  return dispatch_get_main_queue()
}

var GlobalUserInteractiveQueue: dispatch_queue_t {
  return dispatch_get_global_queue(Int(QOS_CLASS_USER_INTERACTIVE.value), 0)
}

var GlobalUserInitiatedQueue: dispatch_queue_t {
  return dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)
}

var GlobalUtilityQueue: dispatch_queue_t {
  return dispatch_get_global_queue(Int(QOS_CLASS_UTILITY.value), 0)
}

var GlobalBackgroundQueue: dispatch_queue_t {
  return dispatch_get_global_queue(Int(QOS_CLASS_BACKGROUND.value), 0)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;回到  &lt;strong&gt;PhotoDetailViewController&lt;/strong&gt;  中的&lt;code&gt;viewDidLoad&lt;/code&gt;中，将&lt;code&gt;dispatch_get_global_queue&lt;/code&gt;和&lt;code&gt;dispatch_get_main_queue&lt;/code&gt;替换为帮助变量：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dispatch_async(GlobalUserInitiatedQueue) {
  let overlayImage = self.faceOverlayImageFromImage(self.image)
  dispatch_async(GlobalMainQueue) {
    self.fadeInNewImage(overlayImage)
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这使得调度调用更易读并且很容易看出在使用哪个队列。&lt;/p&gt;
&lt;h2 id="用dispatch_after推迟任务"&gt;用&lt;code&gt;dispatch_after&lt;/code&gt;推迟任务&lt;/h2&gt;
&lt;p&gt;仔细思考你的 app 中的 UX。用户可能在第一次打开 app 的时候不知道该做什么，不是吗？&lt;/p&gt;

&lt;p&gt;如果在  &lt;strong&gt;PhotoManager&lt;/strong&gt;  类中没有图片的时候，给用户一个提示是个不错的主意。然而，你同时要考虑用户的视线怎样扫过屏幕：如果提示出现的太快，用户可能还在看其他的地方而忽略了提示。&lt;/p&gt;

&lt;p&gt;推迟一秒钟再出现提示，此时便可抓住用户的注意力，因为他们已经对 app 有了第一印象。&lt;/p&gt;

&lt;p&gt;将下面的代码加到&lt;code&gt;showOrHideNavPrompt&lt;/code&gt;的实现中，它位于 &lt;strong&gt;PhotoCollectionViewController.swift&lt;/strong&gt; 文件底部。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func showOrHideNavPrompt() {
  let delayInSeconds = 1.0
  let popTime = dispatch_time(DISPATCH_TIME_NOW,
                              Int64(delayInSeconds * Double(NSEC_PER_SEC))) // 1
  dispatch_after(popTime, GlobalMainQueue) { // 2
    let count = PhotoManager.sharedManager.photos.count
    if count &amp;gt; 0 {
      self.navigationItem.prompt = nil
    } else {
      self.navigationItem.prompt = "Add photos with faces to Googlyify them!"
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;showOrHideNavPrompt&lt;/code&gt;会在&lt;code&gt;viewDidLoad&lt;/code&gt;以及&lt;code&gt;UICollectionView&lt;/code&gt;重新加载的时候被执行。代码解释如下：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;声明推迟的时间。&lt;/li&gt;
&lt;li&gt;等待&lt;code&gt;delayInSeconds&lt;/code&gt;所表示的时间，然后将闭包异步地加入主队列中。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;运行 app。在短暂的延迟后，提示会出现并吸引用户的注意。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dispatch_after&lt;/code&gt;的工作原理就像推迟的&lt;code&gt;dispatch_async&lt;/code&gt;。一旦&lt;code&gt;dispatch_after&lt;/code&gt;返回，你还是无法掌握实际的执行时间抑或是取消任务。&lt;/p&gt;

&lt;p&gt;想知道何时使用&lt;code&gt;dispatch_after&lt;/code&gt;？&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;自定义顺序队列：慎用。在自定义顺序队列中慎用&lt;code&gt;dispatch_after&lt;/code&gt;。你最好留在主队列中。&lt;/li&gt;
&lt;li&gt;主队列（顺序）：好主意。在主队列中使用&lt;code&gt;dispatch_after&lt;/code&gt;是一个好主意；Xcode 对此有自动补全模板。&lt;/li&gt;
&lt;li&gt;并发队列：慎用。很少会这样使用，最好留在主队列中。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="单例和线程安全"&gt;单例和线程安全&lt;/h2&gt;
&lt;p&gt;单例。爱也好，恨也罢，它们在 iOS 中就像猫之于互联网一样流行。:]&lt;/p&gt;

&lt;p&gt;经常有人因为单例不是线程安全的而忧虑。这种担忧是很有道理的，考虑到他们的用法：单例经常被多个控制器同时使用。  &lt;strong&gt;PhotoManager&lt;/strong&gt;  类是一个单例，所以你要仔细思考这个问题。&lt;/p&gt;

&lt;p&gt;思考两种情形，初始化单例的过程和对他进行读写的过程。&lt;/p&gt;

&lt;p&gt;先来看初始化。这看起来很简单，因为 Swift 在全局域中初始化变量。在 Swift 中，全局变量在首次使用时被初始化，并且保证初始化是原子操作。也就是说，初始化代码被视为临界区从而保证了初始化在其他线程使用全局变量之前就完成了。Swift 是怎么做到的？其实，Swift 在幕后使用了 GCD 中的&lt;code&gt;dispatch_once&lt;/code&gt;，详见&lt;a href="https://developer.apple.com/swift/blog/?id=7" rel="nofollow" target="_blank" title=""&gt;博客&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dispatch_once&lt;/code&gt;以线程安全的方式执行且仅执行一次闭包。如果一个线程正处于临界区中 -- 被提交给&lt;code&gt;dispatch_once&lt;/code&gt;的任务 -- 其他线程会阻塞直到它完成。并且一旦它完成，其他线程不会再执行临界区中的代码。用&lt;code&gt;let&lt;/code&gt;将单例定义为全局常量，我们可以进一步保证变量在初始化后不会发生变化。从某种意义上说，所有 Swift 全局常亮量都天生是单例，并且线程安全地初始化。&lt;/p&gt;

&lt;p&gt;但是我们仍需要考虑读和写。尽管 Swift 使用&lt;code&gt;dispatch_once&lt;/code&gt;来确保单例初始化是线程安全的，但不能保证它所表示的数据类型也是线程安全的。例如用一个全局变量来声明一个类实例，但在类中还是会有修改类内部数据的临界区。此时就需要其他方式来达成线程安全，比如通过对数据的同步化使用 (synchronizing access)。&lt;/p&gt;
&lt;h2 id="处理读写问题"&gt;处理读写问题&lt;/h2&gt;
&lt;p&gt;实例化线程安全性不是单例的唯一问题。如果单例的属性表示一个可变对象，比如&lt;code&gt;PhotoManager&lt;/code&gt;中的&lt;code&gt;photos&lt;/code&gt;，那么你就需要考虑那个对象是否线程安全。&lt;/p&gt;

&lt;p&gt;在 Swift 中任意用&lt;code&gt;let&lt;/code&gt;声明的常量都是只读并且线程安全的。用&lt;code&gt;var&lt;/code&gt;声明的变量是可变且非线程安全的，除非数据类型本身被设计成线程安全。Swift 中的集合类型比如&lt;code&gt;Array&lt;/code&gt;和&lt;code&gt;Dictionary&lt;/code&gt;，当声明为变量时不是线程安全的。那么像 Foundation 的容器&lt;code&gt;NSArray&lt;/code&gt;呢？是线程安全的吗？答案是--“可能不是”！Apple 维护的一个&lt;a href="https://developer.apple.com/library/mac/documentation/cocoa/conceptual/multithreading/ThreadSafetySummary/ThreadSafetySummary.html" rel="nofollow" target="_blank" title=""&gt;帮助列表&lt;/a&gt;中有许多 Foundation 中非线程安全的类。&lt;/p&gt;

&lt;p&gt;尽管很多线程可以同时读取一个&lt;code&gt;Array&lt;/code&gt;的可变实例而不出问题，但如果一个线程在修改数组的同时另一个线程却在读取这个数组，这是不安全的。你的单例目前还不能阻止这种情况发生。&lt;/p&gt;

&lt;p&gt;为了弄清楚问题，看看  &lt;strong&gt;PhotoManager.swift&lt;/strong&gt;  中的&lt;code&gt;addPhoto&lt;/code&gt;：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func addPhoto(photo: Photo) {
  _photos.append(photo)
  dispatch_async(dispatch_get_main_queue()) {
    self.postContentAddedNotification()
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是一个 &lt;strong&gt;写&lt;/strong&gt; 方法，因为它修改了一个可变数组。&lt;/p&gt;

&lt;p&gt;再看看&lt;code&gt;photos&lt;/code&gt;属性：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private var _photos: [Photo] = []
var photos: [Photo] {
  return _photos
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个属性的 getter 方法是一个  &lt;strong&gt;读&lt;/strong&gt;  方法。调用者得到一个数组的拷贝并且保护了原始数组不被改变，但是这不能保证一个线程在调用&lt;code&gt;addPhoto&lt;/code&gt;来写的时候没有另一个线程同时也在调用 getter 方法读&lt;code&gt;photos&lt;/code&gt;属性。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt; ：在上面的代码中，为什么调用者要获取&lt;code&gt;photo&lt;/code&gt;数组的拷贝？在 Swift 中，参数或函数返回是通过值或引用来传递的。引用传递和 OC 中的传指针一样，这意味着你得到的是原始的对象，对这个对象的修改会影响到其他使用了这个对象引用的代码。值传递拷贝了对象本身，对拷贝的修改不会影响原始的对象。默认情况下，Swift 类实例是引用传递而结构体是值传递。&lt;/p&gt;

&lt;p&gt;Swift 内置的数据类型，如&lt;code&gt;Array&lt;/code&gt;和&lt;code&gt;Dictionary&lt;/code&gt;，是用结构体来实现的，看起来传递集合类型会造成代码中出现大量的拷贝。不要因此担心内存使用问题。Swift 的集合类型经过优化，只有在需要的时候才进行拷贝，比如通过值传递的数组在第一次被修改的时候。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这是软件开发中经典的&lt;a href="http://en.wikipedia.org/wiki/Readers%E2%80%93writers_problem" rel="nofollow" target="_blank" title=""&gt;读者写者问题&lt;/a&gt;（Readers-Writers Problem）。GCD 使用 &lt;strong&gt;调度屏障&lt;/strong&gt; （dispatch barriers）提供了一个优雅的解决方案来生成&lt;a href="http://en.wikipedia.org/wiki/Read/write_lock_pattern" rel="nofollow" target="_blank" title=""&gt;读写锁&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;当跟并发队列一起工作时，调度屏障是一族行为像序列化瓶颈的函数。使用 GCD 的 barrier API 确保了提交的闭包是指定队列中在特定时段唯一在执行的一个。也就是说必须在所有先于调度屏障提交的任务已经完成的情况下，闭包才能开始执行。&lt;/p&gt;

&lt;p&gt;当轮到闭包时，屏障执行这个闭包并确保队列在此过程不会执行其他任务。一旦闭包完成，队列返回到默认的执行方式。GCD 同时提供了同步和异步两种屏障函数。&lt;/p&gt;

&lt;p&gt;下图说明了屏障函数应用于多个异步任务的效果：&lt;/p&gt;

&lt;p&gt;&lt;img src="http://cdn4.raywenderlich.com/wp-content/uploads/2014/09/Dispatch-Barrier-Swift.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;注意队列开始就像普通的并发队列一样工作。但当屏障执行的时候，队列变成像顺序队列一样。就是说，屏障是唯一一个在执行的任务。在屏障完成后，队列恢复成普通的并发队列。&lt;/p&gt;

&lt;p&gt;下面说明什么时候用 -- 什么时候不应该用 -- 屏障函数：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;自定义顺序队列：坏选择。因为顺序队列本身就是顺序执行，屏障不会起到任何帮助作用。&lt;/li&gt;
&lt;li&gt;全局并发队列：慎用。其他系统可能也在使用队列，你不应该出于自身目的而独占队列。&lt;/li&gt;
&lt;li&gt;自定义并发队列：最佳选择。用于原子操作或是临界区代码。任何需要线程安全的设置和初始化都可以使用屏障。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;因为以上唯一合适的选择就是自定义并发队列，你需要生成一个这样的队列来处理屏障函数以隔离读写操作。并发队列允许多个线程同时的读操作。&lt;/p&gt;

&lt;p&gt;打开 &lt;strong&gt;PhotoManager.swift&lt;/strong&gt; 并在&lt;code&gt;photos&lt;/code&gt;属性下面添加如下私有属性到类中：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private let concurrentPhotoQueue = dispatch_queue_create(
    "com.raywenderlich.GooglyPuff.photoQueue", DISPATCH_QUEUE_CONCURRENT)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用&lt;code&gt;dispatch_queue_create&lt;/code&gt;初始化一个并发队列&lt;code&gt;concurrentPhotoQueue&lt;/code&gt;。第一个参数遵循反向 DNS 命名习惯；保证描述性以利于调试。第二个参数指出你的队列是顺序的还是并发的。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt; ：当在网上搜索例子时，你经常看到人们传&lt;code&gt;0&lt;/code&gt;或&lt;code&gt;NULL&lt;/code&gt;作为&lt;code&gt;dispatch_queue_create&lt;/code&gt;的第二个参数。这是一种过时的方法来生成顺序调度队列；最好用参数显示声明。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;找到&lt;code&gt;addPhoto&lt;/code&gt;并用如下实现替换之：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func addPhoto(photo: Photo) {
  dispatch_barrier_async(concurrentPhotoQueue) { // 1
    self._photos.append(photo) // 2
    dispatch_async(GlobalMainQueue) { // 3
      self.postContentAddedNotification()
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;来看这段代码如何工作的：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;将写操作加入自定义的队列中。当临界区被执行时，这是队列中唯一一个在执行的任务。&lt;/li&gt;
&lt;li&gt;将对象加入数组。因为是屏障闭包，这个闭包不会和&lt;code&gt;concurrentPhotoQueue&lt;/code&gt;中的其他任务同时执行。&lt;/li&gt;
&lt;li&gt;最终发送一个添加了图片的通知。这个通知应该在主线程中发送因为这涉及到 UI，所以这里分派另一个异步任务到主队列中。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这个任务解决了写问题，但是你还需要实现&lt;code&gt;photos&lt;/code&gt;的读方法。&lt;/p&gt;

&lt;p&gt;为确保和写操作保持线程安全，你需要在&lt;code&gt;concurrentPhotoQueue&lt;/code&gt;中执行读操作。但是你需要从函数返回读数据，所以不能异步地提交读操作到队列里，因为异步任务不能保证在函数返回前执行。&lt;/p&gt;

&lt;p&gt;因此，&lt;code&gt;dispatch_sync&lt;/code&gt;是个极好的候选。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dispatch_sync&lt;/code&gt;同步提交任务并等到任务完成后才返回。使用&lt;code&gt;dispatch_sync&lt;/code&gt;和调度屏障一起来跟踪任务；或是在需要等待返回数据时使用&lt;code&gt;dispatch_sync&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;仍需小心。设想你调用&lt;code&gt;dispatch_sync&lt;/code&gt;到当前队列中。这会造成死锁。因为调用在等待闭包完成，但是闭包无法完成（甚至根本没开始！），直到当前在执行的任务结束，但当前任务没法结束（因为阻塞的闭包还没完成）！这就要求你必须清醒的认识到你从哪个队列调用了闭包，以及你将任务提交到哪个队列。&lt;/p&gt;

&lt;p&gt;概述一下何时何地使用&lt;code&gt;dispatch_sync&lt;/code&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;自定义顺序队列：非常小心；如果你在运行一个队列时调用&lt;code&gt;dispatch_sync&lt;/code&gt;调度任务到同一个队列，你显然会制造死锁。&lt;/li&gt;
&lt;li&gt;主队列（顺序）：非常小心，原理同上。&lt;/li&gt;
&lt;li&gt;并发队列：好选择。用在和调度屏障同步或是等待任务完成以继续后续处理。
还是在 &lt;strong&gt;PhotoManager.swift&lt;/strong&gt; 中，替换&lt;code&gt;photos&lt;/code&gt;如下：
&lt;code&gt;
var photos: [Photo] {
var photosCopy: [Photo]!
dispatch_sync(concurrentPhotoQueue) { // 1
photosCopy = self._photos // 2
}
return photosCopy
}
&lt;/code&gt;
分别来看每个号码注释：&lt;/li&gt;
&lt;li&gt;同步调度到&lt;code&gt;concurrentPhotoQueue&lt;/code&gt;队列执行读操作。&lt;/li&gt;
&lt;li&gt;保存图片数组的拷贝到&lt;code&gt;photoCopy&lt;/code&gt;并返回它。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;恭喜 —— 你的&lt;code&gt;PhotoManager&lt;/code&gt;单例已经是线程安全的了。不论你读或是写图片数组，你都有信心保证操作会安全的执行。&lt;/p&gt;
&lt;h2 id="回顾"&gt;回顾&lt;/h2&gt;
&lt;p&gt;还是不能 100% 的确定 GCD 的本质？你可以自己创建使用 GCD 函数的简单例子，通过断点和&lt;code&gt;NSLog&lt;/code&gt;来确保你明白发生了什么。&lt;/p&gt;

&lt;p&gt;我这里有两张动态 GIF 图片来帮助你理解&lt;code&gt;dispatch_async&lt;/code&gt;和&lt;code&gt;dispatch_sync&lt;/code&gt;。每张 GIF 上面都有代码辅助你理解；注意代码中的断点和相应的队列状态。&lt;/p&gt;
&lt;h3 id="重访dispatch_sync"&gt;重访 dispatch_sync&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;override func viewDidLoad() {
  super.viewDidLoad()

  dispatch_sync(dispatch_get_global_queue(
      Int(QOS_CLASS_USER_INTERACTIVE.value), 0)) {

    NSLog("First Log")

  }

  NSLog("Second Log")
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="http://cdn4.raywenderlich.com/wp-content/uploads/2014/08/dispatch_sync_in_action_swift.gif" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;下面对图片中的几个状态做说明：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;主队列按部就班的执行任务 —— 紧接着的任务是实例化包含&lt;code&gt;viewDidLoad&lt;/code&gt;的&lt;code&gt;UIViewController&lt;/code&gt;类。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;viewDidLoad&lt;/code&gt;在主线程中执行。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dispatch_sync&lt;/code&gt;闭包被加入到全局队列中稍后执行。主线程停下来等待闭包完成。同时，全局队列正在并发执行任务；记住闭包以 FIFO 的顺序从全局队列中取出，但是会并发地执行。全局队列首先处理&lt;code&gt;dispatch_sync&lt;/code&gt;闭包加入前已经存在队列中的任务。&lt;/li&gt;
&lt;li&gt;最后，轮到&lt;code&gt;dispatch_sync&lt;/code&gt;闭包执行。&lt;/li&gt;
&lt;li&gt;闭包执行完毕，主线程得以继续。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;viewDidLoad&lt;/code&gt;方法完成，主队列接着处理其它任务。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;dispatch_sync&lt;/code&gt;把任务加入队列并一直等待其完成。&lt;code&gt;dispatch_async&lt;/code&gt;做了差不多的工作，只是它不会等待任务完成，而是转而去继续其他工作。&lt;/p&gt;
&lt;h3 id="重访dispatch_async"&gt;重访 dispatch_async&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;override func viewDidLoad() {
  super.viewDidLoad()

  dispatch_async(dispatch_get_global_queue(
      Int(QOS_CLASS_USER_INTERACTIVE.value), 0)) {

    NSLog("First Log")

  }

  NSLog("Second Log")
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="http://cdn4.raywenderlich.com/wp-content/uploads/2014/08/dispatch_async_in_action_swift.gif" title="" alt=""&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;主队列按部就班的执行任务 —— 紧接着的任务是实例化包含&lt;code&gt;viewDidLoad&lt;/code&gt;的&lt;code&gt;UIViewController&lt;/code&gt;类。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;viewDidLoad&lt;/code&gt;在主线程中执行。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dispatch_async&lt;/code&gt;闭包被加入到全局队列中稍后执行。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;viewDidLoad&lt;/code&gt;在&lt;code&gt;dispatch_async&lt;/code&gt;后继续向下执行，主线程继续其他任务。同时，全局队列正在并发执行任务；记住闭包以 FIFO 的顺序从全局队列中取出，但是会并发地执行。&lt;/li&gt;
&lt;li&gt;执行&lt;code&gt;dispatch_async&lt;/code&gt;所添加的闭包。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dispatch_async&lt;/code&gt;闭包完成，&lt;code&gt;NSLog&lt;/code&gt;输出到控制台。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在这个特别的例子中，第一个&lt;code&gt;NSLog&lt;/code&gt;在第二个&lt;code&gt;NSLog&lt;/code&gt;后执行。事实并非总是如此——这取决于硬件在彼时正在做什么，你无法控制或知晓哪个语句会先执行。“第一个”&lt;code&gt;NSLog&lt;/code&gt;在某种调用情况下可能会先执行。&lt;/p&gt;
&lt;h2 id="下一步？"&gt;下一步？&lt;/h2&gt;
&lt;p&gt;在本教程中，你已经学到了如何编写线程安全的代码以及如何在保持主线程响应性的前提下执行 CPU 密集型的任务。&lt;/p&gt;

&lt;p&gt;可以下载&lt;a href="http://cdn4.raywenderlich.com/wp-content/uploads/2015/01/GooglyPuff_Swift_End_1_Xcode61_start.zip" rel="nofollow" target="_blank" title=""&gt;GooglyPuff&lt;/a&gt;，里面包含了本教程中所做的所有改进。教程的第二部分会在此基础上继续改进。&lt;/p&gt;

&lt;p&gt;如果你打算优化自己的 app，你真的应该使用  &lt;strong&gt;Instruments&lt;/strong&gt; 中的&lt;strong&gt;Time Profile&lt;/strong&gt; 模板来测试。使用方法已经超出本教程范围，可以查看&lt;a href="http://www.raywenderlich.com/?p=23037" rel="nofollow" target="_blank" title=""&gt;怎样使用 Instruments&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;同时确保你在真机上测试，因为在模拟器上测试会得到跟真实体验相差甚远的结果。&lt;/p&gt;

&lt;p&gt;在教程的下篇你会更深入 GCD 的 API 中做些更酷的事情。&lt;/p&gt;

&lt;p&gt;译者：&lt;a href="http://www.loveltyoic.com" rel="nofollow" target="_blank" title=""&gt;loveltyoic&lt;/a&gt;&lt;/p&gt;</description>
      <author>loveltyoic</author>
      <pubDate>Wed, 28 Jan 2015 15:30:47 +0800</pubDate>
      <link>https://ruby-china.org/topics/23989</link>
      <guid>https://ruby-china.org/topics/23989</guid>
    </item>
    <item>
      <title>Newrelic 监测数据的解释</title>
      <description>&lt;p&gt;Rails+Grape 提供 API，用 newrelic 监控。
下面这个 API 的响应达到了 1200ms
&lt;img src="https://l.ruby-china.com/photo/2014/480b172714d14e162b1eaf4d1c19bf76.png" title="" alt=""&gt;
从图中看Rack::ContentLength#call极其耗时，
但是这个 API 只是返回下面的数据
{
    "status": "ok",
    "version": {
        "version_code": "1",
        "version_name": "1",
        "download": "&lt;a href="http://mobile.mst365.cn/app" rel="nofollow" target="_blank"&gt;http://mobile.mst365.cn/app&lt;/a&gt;",
        "desc": "1"
    }
}&lt;/p&gt;

&lt;p&gt;非常困惑为什么？有人遇到过这种情况并了解原因吗？&lt;/p&gt;</description>
      <author>loveltyoic</author>
      <pubDate>Tue, 25 Nov 2014 13:50:36 +0800</pubDate>
      <link>https://ruby-china.org/topics/22855</link>
      <guid>https://ruby-china.org/topics/22855</guid>
    </item>
    <item>
      <title>ActiveRecord 中的 Callback 浅析</title>
      <description>&lt;p&gt;在使用 Rails 或者说 ActiveRecord 的过程中，我们都会用一些 callback 来使我们的代码更紧凑简洁。
我们只需要知道用法就可以了，但是你是否也会好奇，这些神奇的 callback 是怎么实现的呢？
下面通过源码加注释的方式粗略的说明一下我的探究过程。&lt;/p&gt;

&lt;p&gt;首先定义 User 类，并给他添加几个 callback，用了 3 种形式&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShowMyself&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;before_validation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'def callback by object'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;before_create&lt;/span&gt; &lt;span class="ss"&gt;:say_hello&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;say_hello&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'def callback by method'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;before_validation&lt;/span&gt; &lt;span class="no"&gt;ShowMyself&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

  &lt;span class="n"&gt;before_save&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; 
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'def callback by block'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么问题来了，&lt;code&gt;before_validation ShowMyself.new&lt;/code&gt;是怎么工作的呢？&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;从&lt;code&gt;before_validation&lt;/code&gt;开始查找&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;before_validation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:on&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:if&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:if&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:on&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:on&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:if&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;unshift&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:on&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validation_context&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;set_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:validation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:before&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# set_callback(:validation, :before, ShowMyself.new)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;进入&lt;code&gt;set_callback(:validation, :before, ShowMyself.new)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;filter_list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# name = :validation&lt;/span&gt;
  &lt;span class="c1"&gt;# filter_list = [:before, ShowMyself.new]&lt;/span&gt;

  &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize_callback_params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filter_list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# 调用normalize_callback_params，得到如下返回&lt;/span&gt;

  &lt;span class="c1"&gt;# type = :before&lt;/span&gt;
  &lt;span class="c1"&gt;# filters = [ShowMyself.new]&lt;/span&gt;
  &lt;span class="c1"&gt;# options = {}&lt;/span&gt;

  &lt;span class="n"&gt;self_chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_callbacks&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;

  &lt;span class="c1"&gt;# get_callbacks 得到 CallbackChain类型的实例, &lt;/span&gt;
  &lt;span class="c1"&gt;# 在console里面通过User._validation_callbacks就可以得到它了&lt;/span&gt;
  &lt;span class="c1"&gt;# 其他的如_save_callbacks, _create_callbacks都一样可以看到&lt;/span&gt;

  &lt;span class="n"&gt;mapped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="no"&gt;Callback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self_chain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Callback.build(self_chain, ShowMyself.new, :before, {})&lt;/span&gt;
    &lt;span class="c1"&gt;# 这里可以暂时知道它是一个Callback实例就可以了，后面会有说明&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="c1"&gt;# mapped = [以ShowMyself.new为filter的Callback实例]&lt;/span&gt;

  &lt;span class="c1"&gt;# 记住name = :validation&lt;/span&gt;
  &lt;span class="n"&gt;__update_callbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="c1"&gt;# 在当前block中&lt;/span&gt;
    &lt;span class="c1"&gt;# target = User&lt;/span&gt;
    &lt;span class="c1"&gt;# chain = User.get_callbacks :validation&lt;/span&gt;

    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:prepend&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mapped&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mapped&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 把mapped加入到回调链中&lt;/span&gt;

    &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_callbacks&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;
    &lt;span class="c1"&gt;# 即User._validation_callbacks= chain&lt;/span&gt;
    &lt;span class="c1"&gt;# 执行到这里，ShowMyself.new就添加成功了&lt;/span&gt;
    &lt;span class="c1"&gt;# 可以执行User._validation_callbacks.first.filter看看&lt;/span&gt;
    &lt;span class="c1"&gt;# 应该看到类似如下结果&lt;/span&gt;
    &lt;span class="c1"&gt;# &amp;lt;ShowMyself:0x007ff556309938&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;# 正是ShowMyself的实例&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# File activesupport/lib/active_support/callbacks.rb, line 740&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_callbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callbacks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;send&lt;/span&gt; &lt;span class="s2"&gt;"_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_callbacks="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callbacks&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;normalize_callback_params&lt;/code&gt;
```ruby&lt;br&gt;
def normalize_callback_params(filters, block) # :nodoc:
type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before
# type = filters 中 (即传过来的 [:before, ShowMyself.new]) 第一个元素 = :before&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;options = filters.extract_options!
  filters.unshift(block) if block
  [type, filters, options.dup]
end&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- `__update_callbacks`
```ruby
def __update_callbacks(name) #:nodoc:
  ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse_each do |target|
    #当前类User以及他的子类都需要新增这个callback, 即回调也是会继承的
    chain = target.get_callbacks name
    yield target, chain.dup
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上是定义 callback 的过程，那么 callback 是怎么执行的呢？&lt;/p&gt;

&lt;p&gt;当执行 user.valid？的时候，其实调用了&lt;code&gt;run_validations!&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_validations!&lt;/span&gt; &lt;span class="c1"&gt;#:nodoc:&lt;/span&gt;
  &lt;span class="n"&gt;_run_validation_callbacks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_run_&lt;/span&gt;&lt;span class="c1"&gt;#{name}_callbacks(&amp;amp;block)&lt;/span&gt;
  &lt;span class="n"&gt;_run_callbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="c1"&gt;#{name}_callbacks, &amp;amp;block)&lt;/span&gt;
  &lt;span class="c1"&gt;# _run_callbacks(_validation_callbacks, &amp;amp;block)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_run_callbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callbacks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# 这里的callbacks就是User._validation_callbacks&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;callbacks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;
    &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;runner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;callbacks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;
    &lt;span class="c1"&gt;# compile 是CallbackChain中定义的实例方法,返回一个lambda&lt;/span&gt;

    &lt;span class="c1"&gt;# runner就是如下形式的lambda&lt;/span&gt;
    &lt;span class="c1"&gt;# lambda { |env|&lt;/span&gt;
    &lt;span class="c1"&gt;#   user_callback.call env.target, env.value&lt;/span&gt;
    &lt;span class="c1"&gt;#   next_callback.call env&lt;/span&gt;
    &lt;span class="c1"&gt;# }&lt;/span&gt;

    &lt;span class="c1"&gt;# user_callback也是一个lambda&lt;/span&gt;
    &lt;span class="c1"&gt;# lambda { |target, _, &amp;amp;blk|&lt;/span&gt;
    &lt;span class="c1"&gt;#    filter.public_send method_to_call, target, &amp;amp;blk&lt;/span&gt;
    &lt;span class="c1"&gt;# }&lt;/span&gt;

    &lt;span class="c1"&gt;# 当前的self就是User实例，即user&lt;/span&gt;
    &lt;span class="c1"&gt;# Environment从后面可以知道就是一个Struct&lt;/span&gt;
    &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Filters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;CallbackChain 的定义 (节选)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CallbackChain&lt;/span&gt; &lt;span class="c1"&gt;#:nodoc:#&lt;/span&gt;
  &lt;span class="c1"&gt;# 一个可遍历的链&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Enumerable&lt;/span&gt;

  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:config&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compile&lt;/span&gt;
    &lt;span class="vi"&gt;@callbacks&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="vi"&gt;@mutex.synchronize&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="vi"&gt;@callbacks&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="vi"&gt;@chain.reverse.inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Filters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ENDING&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;
        &lt;span class="c1"&gt;#进入Callback::apply&lt;/span&gt;
        &lt;span class="c1"&gt;#此时&lt;/span&gt;
        &lt;span class="c1"&gt;#callback.name = :validation&lt;/span&gt;
        &lt;span class="c1"&gt;#callback.kind = :before&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Callback&lt;/span&gt; &lt;span class="c1"&gt;#:nodoc:#&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;
    &lt;span class="c1"&gt;# 此时&lt;/span&gt;
    &lt;span class="c1"&gt;# chain.name = :validation&lt;/span&gt;
    &lt;span class="c1"&gt;# filter = ShowMyself.new&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;
  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:chain_config&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chain_config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@chain_config&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain_config&lt;/span&gt;
    &lt;span class="vi"&gt;@name&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;
    &lt;span class="vi"&gt;@kind&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;
    &lt;span class="vi"&gt;@filter&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt;
    &lt;span class="vi"&gt;@key&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;compute_identifier&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt;
    &lt;span class="vi"&gt;@if&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:if&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="vi"&gt;@unless&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:unless&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;user_conditions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conditions_lambdas&lt;/span&gt;
    &lt;span class="n"&gt;user_callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_lambda&lt;/span&gt; &lt;span class="vi"&gt;@filter&lt;/span&gt;
    &lt;span class="c1"&gt;# 当前的@filter 就是ShowMyself.new&lt;/span&gt;
    &lt;span class="c1"&gt;# make_lambda很关键！定义在下面&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:before&lt;/span&gt;
      &lt;span class="no"&gt;Filters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Before&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_conditions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chain_config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@filter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:after&lt;/span&gt;
      &lt;span class="no"&gt;Filters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;After&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_conditions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chain_config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:around&lt;/span&gt;
      &lt;span class="no"&gt;Filters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Around&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_conditions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chain_config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_lambda&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt;
    &lt;span class="c1"&gt;# 当filter是一个方法的时候, 如 :say_hello, 执行user.send :say_hello&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="no"&gt;Symbol&lt;/span&gt;
      &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;blk&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;blk&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;# 可以用string来定义写filter，用eval执行&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;
      &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"lambda { |value| &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; }"&lt;/span&gt;
      &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="no"&gt;Conditionals&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Value&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Proc&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arity&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ArgumentError&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;
          &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arity&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="c1"&gt;# ShowMyself.new的情况&lt;/span&gt;
      &lt;span class="c1"&gt;# scope=&amp;gt;[:kind, :name]&lt;/span&gt;
      &lt;span class="n"&gt;scopes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chain_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:scope&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="n"&gt;method_to_call&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;public_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"_"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="c1"&gt;# method_to_call = "before_validation"&lt;/span&gt;

      &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;blk&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;public_send&lt;/span&gt; &lt;span class="n"&gt;method_to_call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;blk&lt;/span&gt;
        &lt;span class="c1"&gt;# ShowMyself.new.public_send "before_validation", user&lt;/span&gt;
        &lt;span class="c1"&gt;# 看这个lambda, 再回头看看ShowMyself里&lt;/span&gt;
        &lt;span class="c1"&gt;# def before_validation(model)&lt;/span&gt;
        &lt;span class="c1"&gt;#   p model.inspect&lt;/span&gt;
        &lt;span class="c1"&gt;#   puts 'def callback by class'&lt;/span&gt;
        &lt;span class="c1"&gt;# end&lt;/span&gt;
        &lt;span class="c1"&gt;# 转了一圈，终于真相大白了&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="附录"&gt;附录&lt;/h2&gt;
&lt;p&gt;callback 的主要实现都在 ActiveSupport::Callbacks 中&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# activrecord/lib/active_record/callbacks.rb&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ActiveRecord&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Base&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Callbacks&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Callbacks&lt;/span&gt;
    &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;
    &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ClassMethods&lt;/span&gt;
      &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;ActiveModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Callbacks&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="c1"&gt;# 上面翻译过来就是 ActiveRecord::Base.extend ActiveModel::Callbacks&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# activemodel/lib/active_model/callbacks.rb&lt;/span&gt;

&lt;span class="n"&gt;moudle&lt;/span&gt; &lt;span class="no"&gt;ActiveModel&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Callbacks&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extended&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;#:nodoc:&lt;/span&gt;
      &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class_eval&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Callbacks&lt;/span&gt;
        &lt;span class="c1"&gt;# 结果就是 ActiveRecord::Base.include ActiveSupport::Callbacks&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;</description>
      <author>loveltyoic</author>
      <pubDate>Fri, 21 Nov 2014 21:32:53 +0800</pubDate>
      <link>https://ruby-china.org/topics/22809</link>
      <guid>https://ruby-china.org/topics/22809</guid>
    </item>
    <item>
      <title>帮忙看下这段代码是否有更好的实现方式</title>
      <description>&lt;p&gt;先描述下需求：项目中用 sidekiq 做后台任务，大概有十几个&lt;code&gt;xxxJob&lt;/code&gt; 的 class，每个 job 都定义好了 perform 方法。
现在需要对每个 job 做些 log 操作。
我的做法是定义一个 module，然后让每个 job include 这个 module。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;JobLogger&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;

  &lt;span class="n"&gt;included&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="kp"&gt;alias_method&lt;/span&gt; &lt;span class="ss"&gt;:perform_without_log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:perform&lt;/span&gt;
    &lt;span class="k"&gt;undef&lt;/span&gt; &lt;span class="n"&gt;perform&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;perform_without_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;log操作&lt;/span&gt; 
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这么做的问题是，在每个 xxxJob 里，必须要像下面这样&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;XXXJob&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;
    &lt;span class="n"&gt;xxx&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;JobLogger&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;感觉把 include 写在中间有些别扭，有没有更优雅的实现方式？&lt;/p&gt;</description>
      <author>loveltyoic</author>
      <pubDate>Wed, 09 Jul 2014 20:16:12 +0800</pubDate>
      <link>https://ruby-china.org/topics/20401</link>
      <guid>https://ruby-china.org/topics/20401</guid>
    </item>
    <item>
      <title>请问 Ruby-China 里的 Faye 如何防窃听的？</title>
      <description>&lt;p&gt;下面两段代码是我在 ruby-china 源码中看到的。
如果没理解错的话，这里由服务器端生成一个临时的 token，保证当前用户只能订阅到分配给自己的频道，假设有恶意用户想窃听其他用户的频道，但由于他没有这个 token，是找不到那个有效频道的，这个 token 就相当于秘密电台，只有当前用户和站点之间知道。
我的问题是：在客户端的 js 中，那个&lt;code&gt;App.access_token&lt;/code&gt;是怎么获取的？
客户端&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;initNotificationSubscribe&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
  &lt;span class="nx"&gt;faye&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Faye&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;faye_client_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;faye&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/notifications_count/#{App.access_token}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;span&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#user_notifications_count span&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;new_title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\(\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\)&lt;/span&gt;&lt;span class="sr"&gt; /&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;badge-error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;new_title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;(#{json.count}) #{new_title}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fixUrlDash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#{App.root_url}#{json.content_path}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;
      &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;badge-error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;new_title&lt;/span&gt;
  &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;服务器&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;realtime_push_to_client&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;
    &lt;span class="nb"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify_hash&lt;/span&gt;
    &lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:count&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
    &lt;span class="no"&gt;FayeClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/notifications_count/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;temp_access_token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;</description>
      <author>loveltyoic</author>
      <pubDate>Tue, 08 Jul 2014 13:08:18 +0800</pubDate>
      <link>https://ruby-china.org/topics/20372</link>
      <guid>https://ruby-china.org/topics/20372</guid>
    </item>
    <item>
      <title>D3 中的数据绑定--enter () 和 exit ()</title>
      <description>&lt;p&gt;最近的几个项目都用到了 D3，各种各样的图也画了一些，算是积攒了一点经验。
跟需要的同学分享一下，讲的不对的地方欢迎指教。
&lt;a href="http://loveltyoic.com/blog/2014/05/10/d3-data-binding/" rel="nofollow" target="_blank"&gt;http://loveltyoic.com/blog/2014/05/10/d3-data-binding/&lt;/a&gt;&lt;/p&gt;</description>
      <author>loveltyoic</author>
      <pubDate>Sat, 10 May 2014 23:21:07 +0800</pubDate>
      <link>https://ruby-china.org/topics/19159</link>
      <guid>https://ruby-china.org/topics/19159</guid>
    </item>
    <item>
      <title>怎么做 like 这个功能？</title>
      <description>&lt;p&gt;对这样一种 many to many 的关系
第一种方式：在被 like 的客体中加入一个 count 字段，来统计这个客体被多少主题 like 了，每次查询直接根据客体的 id，来找到这条记录，读出 count 字段。
另一种方式：不设置 count 字段，而是通过查询关联记录的条数来获取这个 count。
大家来谈谈看法。&lt;/p&gt;</description>
      <author>loveltyoic</author>
      <pubDate>Fri, 21 Mar 2014 14:06:37 +0800</pubDate>
      <link>https://ruby-china.org/topics/18066</link>
      <guid>https://ruby-china.org/topics/18066</guid>
    </item>
    <item>
      <title>如何做一些简单的网站图标？</title>
      <description>&lt;p&gt;如题。
有专门的应用吗？还是说一定要掌握 PS。
当然不涉及复杂的设计，只是很简单的。
能否推荐一下应用或是教程，感谢！&lt;/p&gt;</description>
      <author>loveltyoic</author>
      <pubDate>Wed, 13 Nov 2013 13:14:42 +0800</pubDate>
      <link>https://ruby-china.org/topics/15508</link>
      <guid>https://ruby-china.org/topics/15508</guid>
    </item>
    <item>
      <title>mongoid 的 or 和 and 嵌套查询怎么写？</title>
      <description>&lt;p&gt;比如有一个时间段，用 c_start 和 c_end 表示开始和结束时间，然后每条数据也有一个开始和结束时间，我现在想查找：
开始时间在 c_start 和 c_end 之间 
or
结束时间在 c_start 和 c_end 之间
目前我的写法如下，可以工作了，但是感觉不够简洁，想知道有没有更简洁的写法？&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;in_callendar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;any_of&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="s2"&gt;"$and"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="s2"&gt;"start"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"$gte"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c_start&lt;/span&gt; &lt;span class="p"&gt;}},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"start"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"$lte"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c_end&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"$and"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="s2"&gt;"end"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"$gte"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c_start&lt;/span&gt; &lt;span class="p"&gt;}},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"end"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"$lte"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c_end&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;</description>
      <author>loveltyoic</author>
      <pubDate>Sun, 20 Oct 2013 16:29:39 +0800</pubDate>
      <link>https://ruby-china.org/topics/14882</link>
      <guid>https://ruby-china.org/topics/14882</guid>
    </item>
    <item>
      <title>New Relic 送的 T 恤能寄到中国么？</title>
      <description>&lt;p&gt;之前看到说部署了就送 3 个月的 Code School，就注册了一下，今天才用上，结果还给我发了邮件说要送 T 恤。
反正我就把国内地址填了，万一送到了呢。&lt;/p&gt;</description>
      <author>loveltyoic</author>
      <pubDate>Fri, 18 Oct 2013 20:49:18 +0800</pubDate>
      <link>https://ruby-china.org/topics/14848</link>
      <guid>https://ruby-china.org/topics/14848</guid>
    </item>
    <item>
      <title>应用模板</title>
      <description>&lt;p&gt;我们在创建应用时，一般都会有相同的配置，比如使用 mongoid，rspec 等等，怎样把一个已有应用的 gem 以及 config 做成模板呢？
这样在创建一个新应用时，就不需要再手动的添加这些配置，只需要稍微修改一下就好了。
我看到 Rails Wizard 和 App Scrolls 只提供了有限的一些 gem，大家是怎么解决这个问题的呢？&lt;/p&gt;</description>
      <author>loveltyoic</author>
      <pubDate>Fri, 20 Sep 2013 10:24:41 +0800</pubDate>
      <link>https://ruby-china.org/topics/14241</link>
      <guid>https://ruby-china.org/topics/14241</guid>
    </item>
    <item>
      <title>搞不定了，问个 asset 的问题</title>
      <description>&lt;p&gt;描述一下，我在&lt;code&gt;layout/application.html.erb&lt;/code&gt;里用下面的代码引入 js 和 css&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;%= stylesheet_link_tag "application", :media =&amp;gt; "all" %&amp;gt;
&amp;lt;%= javascript_include_tag "application" %&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之前都是在 development 环境下，没有问题。今天换做 production 环境，首先&lt;code&gt;rake assets:precompile&lt;/code&gt;了，在&lt;code&gt;public/assets&lt;/code&gt;目录下生成了 js 和 css。
当我启动&lt;code&gt;rails s -e=production&lt;/code&gt;后，发现 css 和 js 都没加载，一看源码，发现变成下面这样了&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link href="/stylesheets/application.css" media="all" rel="stylesheet" type="text/css" /&amp;gt;
&amp;lt;script src="/javascripts/application.js" type="text/javascript"&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就是说通过&lt;code&gt;stylesheet_link_tag&lt;/code&gt;生成的路径与编译出来的路径不符！可是我在开发环境下一直没问题，看源码也是像下面这样的：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link href="/assets/application.css?body=1" media="all" rel="stylesheet" type="text/css" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;是什么原因导致开发环境和生产环境下，这个 stylesheet_link_tag 生成的路径不一样呢？
即开发环境是预期的&lt;code&gt;/asset&lt;/code&gt;，而生产环境却变成了不正常的&lt;code&gt;/javascripts&lt;/code&gt; 。&lt;/p&gt;</description>
      <author>loveltyoic</author>
      <pubDate>Tue, 20 Aug 2013 23:48:38 +0800</pubDate>
      <link>https://ruby-china.org/topics/13476</link>
      <guid>https://ruby-china.org/topics/13476</guid>
    </item>
    <item>
      <title>头像的问题</title>
      <description>&lt;p&gt;刚在这里注册的时候还不能翻墙设置&lt;code&gt;gravatar.com&lt;/code&gt;，就上传了一个本地的头像。
但是现已经在&lt;code&gt;gravatar.com&lt;/code&gt;上设置了头像，在&lt;code&gt;github&lt;/code&gt;上可以显示。
但是发现这里的头像没有更新，怎么解？
当然再上传一个本地的也可以，只是觉得应该有一个选项，
让用户选择是用&lt;code&gt;gravatar&lt;/code&gt;的头像，还是本地上传的头像。
或者干脆取消本地上传，像&lt;code&gt;github&lt;/code&gt;一样。&lt;/p&gt;</description>
      <author>loveltyoic</author>
      <pubDate>Fri, 26 Jul 2013 11:43:46 +0800</pubDate>
      <link>https://ruby-china.org/topics/12795</link>
      <guid>https://ruby-china.org/topics/12795</guid>
    </item>
    <item>
      <title>如何在结果集中再次查询？</title>
      <description>&lt;p&gt;首先，我已经在一个 action 中通过 where 查询出了一个结果集&lt;code&gt;@results&lt;/code&gt;
这时，我想在结果页面中再次添加一个查询条件，然后在 &lt;code&gt;@results&lt;/code&gt;的基础上再次查询.
例如，我在网上选显示器，首先我按品牌查询出了一个结果页面，然后在结果页面中再按尺寸来查找。
该怎样把结果集传到再次查询的另一个 action 中呢？&lt;/p&gt;</description>
      <author>loveltyoic</author>
      <pubDate>Thu, 11 Jul 2013 23:12:12 +0800</pubDate>
      <link>https://ruby-china.org/topics/12444</link>
      <guid>https://ruby-china.org/topics/12444</guid>
    </item>
  </channel>
</rss>
