一个大小写引发的血案

在我刚开始写ruby的时候,完全不知道ruby里面的命名规则是个什么鬼,在最开始的时候,有一些文件还是按照自己的习惯给文件名大写开头。这个在本机跑的时候没有任何问题,但是一旦开始要与其他类库交叉使用的时候,问题就来了。

首先是sidekiq, 总是提示Cannot define multiple 'included' blocks for a Concern,搜索了很多,一直解决不了,后来不得不提了个issue sidekiq#2942,大家在最后也可以看到得到了解答,去把大写开头的文件改成了小写。

我以为这就结束了,然而并没有。。我还是太naive了!这个问题之后遇到了一些问题,比如:如何:一个server上配置多个github-deploy-key。这个问题的后半部分关于ssh-agent和代号使用问题也是我血肉之躯趟过的呀,最后的最后我仍然还有一个疑问,我甚至还给了github support发了邮件。下面是我的一个阐述:

Hi John,

Thanks for the response. I realized that I may have made some mistake. I ran the ‘git clone git@github.com:LinkFirms/Apollo.git’ instead of ‘git clone git@apollo:LinkFirms/Apollo.git’. If I use the latter, It both work.

But I use git clone git@github.com:LinkFirms/Apollo.git because It seem to be the way my automatic deployment tool did. In the settings file:

1
2
3
set :repository, 'git@github.com:LinkFirms/Apollo.git'
set :branch, 'release'
set :forward_agent, true

and here is the mina code . As the code run , I think It goes ‘git clone git@github.com:LinkFirms/Apollo.git’ finally, But what confused me is that the deployment did work!

So What caused the difference?
(If I don’t make myself clear, please let me know.)

Thanks,
even

github的ssh配置解决之后,万事大吉,只欠东风!但是,又碰到了一个问题

1
! Unable to load application: NameError: uninitialized constant Api::V1::BaseController::Authenticatable

后来为了搞清楚到底是为什么,我把自己的mac环境也换成了puma来跑,一切都很正常,那到底什么为什么呢,是运行环境的问题,那行,我也用production环境跑,也没问题呀!这到底是为什么?!

于是,我又去打扰大神去提了个issue puma#972 正如你们所见,他们说不关puma的事情。

我自己继续琢磨,觉得应该是部署环境的文件问题,导致了跟sidekiq同样的加载问题。所以我去查看了github上的release分支上这个对应的文件。事实证明:文件名还是大写!文件名还是大写!文件名还是大写!这不是坑爹呐!摔!请自行脑补我的反应。。

原来文件系统对大小写并不做区分,所以这个改动并没有push到github,参照 Case sensitivity in Git 之后,我终于完成了此次在一台服务器上部署多个项目的进程。大概都要崩溃了。。

多么痛的领悟

如何:一个server上配置多个github deploy key

如果您是一个github的使用者,那么你一定会接触到deploy key这个概念。deploy key基本就是github的权限控制了,被授予的机器才能有读或读写的能力。

如果你的server上只部署一个app的话,那用起来就很简单:

  1. 通过ssh-keygen命令生成rsa凭证到 .ssh/ 文件夹下
  2. 拷贝对应的公钥文件内容
  3. 进入github的项目Settings中,选择左边栏Deploy keys
  4. 点击Add deploy keys,复制即可

理论上以上四个步骤就足够,不过你还可以运行以下命令来测试ssh连接

1
ssh -T git@github.com

如果一切顺利,你将会得到:

1
Hi LinkFirms/Apollo! You've successfully authenticated, but GitHub does not provide shell access.

在大多数情况下以上就够用了,但是 …(是的,总是会有一个但是的)

如果当你碰到某种情景(比如不能土豪到一个app装在一个server上,同一个github组织下的两个项目等),需要将多个app在同一个server上host,那么你可能遇到一个麻烦:如何让命令知道我的另外一个项目的存在。事实上如果不做任何事情的话,你将总会遇到repo不存在或者检查权限的提示。经过一些搜索之后,借鉴 USING MULTIPLE GITHUB DEPLOY KEYS FOR A SINGLE USER ON A SINGLE LINUX SERVER 这篇文章大概找到了解决方案。方法是通过ssh config的方式,来指定对应的请求,感兴趣的可以了解一下什么是ssh config:OpenSSH Config File Examples。经试验,最终的解决方案与文中提供的答案略有出入。

步骤如下:

  1. 按照之前的步骤为项目B再单独进行一遍,这样你就有了两对ssh key和秘钥,并均已添加到对应github项目的设定中。
  2. 在 .ssh/ 文件夹下创建 config 文件。
  3. 编辑config文件。

示例config文件如下;

1
2
3
4
5
6
7
8
9
Host manhattan
HostName github.com
User git
IdentityFile /home/deploy/.ssh/id_rsa_manhattan

Host apollo
HostName github.com
User git
IdentityFile /home/deploy/.ssh/id_rsa_apollo

其中manhattan和apollo是两个项目配置的代号,在测试ssh连接的时候可以用这两个代号。比如:

1
2
3
4
deploy@iZ28lie0h28Z:~$ ssh -T apollo
Hi LinkFirms/Apollo! You've successfully authenticated, but GitHub does not provide shell access.
deploy@iZ28lie0h28Z:~$ ssh -T manhattan
Hi LinkFirms/Manhatton! You've successfully authenticated, but GitHub does not provide shell access.

如此!便大功告成!


可是还是有很多无辜的人说:为神马还是不行。解决方案来啦:首先请确定不是 ssh-agent 的问题,然后请不要再使用

1
git clone git@github.com:LinkFirms/Apollo.git

而是使用

1
git clone git@apollo:LinkFirms/Apollo.git

使用原来的方式的话,如果多个项目在同一个域名下(比如github.com),那么你只能连接到最先出现的那个项目。所以你需要以代号的方式,那样就能正确解析到其他的项目了。其实如果在测试连接的时候,打开详细说明的话就能看出一些问题,运行以下命令:

1
ssh -vT apollo

这样的话,就会把详细的流程打印出来,就会发现ssh在找到第一个匹配之后就不会再找了。

locale: Cannot set LC_CTYPE to default locale: No such file or directory

我们的阿里云机器是Ubuntu 11.04,经常会遇到这个问题:

1
2
3
4
5
6
7
8
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = "en_US:",
LC_ALL = (unset),
LC_CTYPE = "zh_CN.UTF-8",
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").

输入命令:

1
locale

输出结果是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory
LANG=en_US.UTF-8
LANGUAGE=en_US:
LC_CTYPE=zh_CN.UTF-8
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=

我们看到的确有两个找不到的warning,但是也不知道是为啥。最后对我真正起作用的是这个答案:Ubuntu server strange locale issue [closed]

看起来我们缺少了LC_ALL,所以我只运行了以下命令:

1
export LC_ALL=en_US.UTF-8

最后解决了问题。给大家以供参考

后注

以上也未能解决以上问题,大家可以参考这个链接 How to fix Perl warning setting locale failed on Raspbian

Sidekiq: Cannot define multiple 'included' blocks for a Concern

在了解了Sidekiq的强大之后,开始着手使用,谁知道一开始就碰上了问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
2016-04-21T10:23:40.101Z 16720 TID-oum74s8io INFO: Booting Sidekiq 4.1.1 with redis options {:namespace=>"sidekiq", :url=>"redis://127.0.0.1:6379/0"}
Cannot define multiple 'included' blocks for a Concern
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/concern.rb:126:in `included'
/Users/evenluo/Develop/Link Firms/Apollo/app/controllers/concerns/Authenticatable.rb:4:in `<module:Authenticatable>'
/Users/evenluo/Develop/Link Firms/Apollo/app/controllers/concerns/Authenticatable.rb:1:in `<top (required)>'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:457:in `load'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:457:in `block in load_file'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:647:in `new_constants_in'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:456:in `load_file'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:354:in `require_or_load'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:317:in `depend_on'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:233:in `require_dependency'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/engine.rb:472:in `block (2 levels) in eager_load!'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/engine.rb:471:in `each'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/engine.rb:471:in `block in eager_load!'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/engine.rb:469:in `each'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/engine.rb:469:in `eager_load!'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/engine.rb:346:in `eager_load!'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/application/finisher.rb:56:in `each'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/application/finisher.rb:56:in `block in <module:Finisher>'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/initializable.rb:30:in `instance_exec'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/initializable.rb:30:in `run'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/initializable.rb:55:in `block in run_initializers'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:228:in `block in tsort_each'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:350:in `block (2 levels) in each_strongly_connected_component'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:431:in `each_strongly_connected_component_from'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:349:in `block in each_strongly_connected_component'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:347:in `each'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:347:in `call'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:347:in `each_strongly_connected_component'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:226:in `tsort_each'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:205:in `tsort_each'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/initializable.rb:54:in `run_initializers'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/application.rb:352:in `initialize!'
/Users/evenluo/Develop/Link Firms/Apollo/config/environment.rb:5:in `<top (required)>'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:274:in `require'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:274:in `block in require'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:240:in `load_dependency'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:274:in `require'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/sidekiq-4.1.1/lib/sidekiq/cli.rb:233:in `boot_system'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/sidekiq-4.1.1/lib/sidekiq/cli.rb:49:in `run'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/sidekiq-4.1.1/bin/sidekiq:12:in `<top (required)>'
/Users/evenluo/.rvm/gems/ruby-2.3.0/bin/sidekiq:22:in `load'
/Users/evenluo/.rvm/gems/ruby-2.3.0/bin/sidekiq:22:in `<main>'
/Users/evenluo/.rvm/gems/ruby-2.3.0/bin/ruby_executable_hooks:15:in `eval'
/Users/evenluo/.rvm/gems/ruby-2.3.0/bin/ruby_executable_hooks:15:in `<main>'

这是多么地让人心痛啊。。

Ok, Let’s google this. And find one possible solution: Fix Sidekiq loading problem: Cannot define multiple included blocks for a Concern

不过不幸的是,两个都未能解决了问题。所以我尝试打印一下是谁调用了这个文件,得到了这个:

调用方1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
FILE /Users/evenluo/Develop/Link Firms/Apollo/app/controllers/concerns/authenticatable.rb LOADED BY:
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:274:in `require'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:274:in `block in require'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:240:in `load_dependency'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:274:in `require'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:360:in `require_or_load'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:494:in `load_missing_constant'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:184:in `const_missing'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:526:in `load_missing_constant'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:184:in `const_missing'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:526:in `load_missing_constant'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:184:in `const_missing'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:526:in `load_missing_constant'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:184:in `const_missing'
/Users/evenluo/Develop/Link Firms/Apollo/app/controllers/api/v1/base_controller.rb:2:in `<class:BaseController>'
/Users/evenluo/Develop/Link Firms/Apollo/app/controllers/api/v1/base_controller.rb:1:in `<top (required)>'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:274:in `require'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:274:in `block in require'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:240:in `load_dependency'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:274:in `require'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:360:in `require_or_load'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:317:in `depend_on'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:233:in `require_dependency'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/engine.rb:472:in `block (2 levels) in eager_load!'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/engine.rb:471:in `each'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/engine.rb:471:in `block in eager_load!'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/engine.rb:469:in `each'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/engine.rb:469:in `eager_load!'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/engine.rb:346:in `eager_load!'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/application/finisher.rb:56:in `each'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/application/finisher.rb:56:in `block in <module:Finisher>'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/initializable.rb:30:in `instance_exec'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/initializable.rb:30:in `run'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/initializable.rb:55:in `block in run_initializers'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:228:in `block in tsort_each'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:350:in `block (2 levels) in each_strongly_connected_component'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:431:in `each_strongly_connected_component_from'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:349:in `block in each_strongly_connected_component'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:347:in `each'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:347:in `call'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:347:in `each_strongly_connected_component'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:226:in `tsort_each'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:205:in `tsort_each'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/initializable.rb:54:in `run_initializers'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/application.rb:352:in `initialize!'
/Users/evenluo/Develop/Link Firms/Apollo/config/environment.rb:5:in `<top (required)>'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:274:in `require'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:274:in `block in require'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:240:in `load_dependency'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:274:in `require'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/sidekiq-4.1.1/lib/sidekiq/cli.rb:233:in `boot_system'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/sidekiq-4.1.1/lib/sidekiq/cli.rb:49:in `run'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/sidekiq-4.1.1/bin/sidekiq:12:in `<top (required)>'
/Users/evenluo/.rvm/gems/ruby-2.3.0/bin/sidekiq:22:in `load'
/Users/evenluo/.rvm/gems/ruby-2.3.0/bin/sidekiq:22:in `<main>'
/Users/evenluo/.rvm/gems/ruby-2.3.0/bin/ruby_executable_hooks:15:in `eval'
/Users/evenluo/.rvm/gems/ruby-2.3.0/bin/ruby_executable_hooks:15:in `<main>'

调用方2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
FILE /Users/evenluo/Develop/Link Firms/Apollo/app/controllers/concerns/Authenticatable.rb LOADED BY:
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:274:in `require'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:274:in `block in require'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:240:in `load_dependency'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:274:in `require'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:360:in `require_or_load'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:317:in `depend_on'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:233:in `require_dependency'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/engine.rb:472:in `block (2 levels) in eager_load!'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/engine.rb:471:in `each'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/engine.rb:471:in `block in eager_load!'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/engine.rb:469:in `each'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/engine.rb:469:in `eager_load!'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/engine.rb:346:in `eager_load!'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/application/finisher.rb:56:in `each'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/application/finisher.rb:56:in `block in <module:Finisher>'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/initializable.rb:30:in `instance_exec'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/initializable.rb:30:in `run'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/initializable.rb:55:in `block in run_initializers'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:228:in `block in tsort_each'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:350:in `block (2 levels) in each_strongly_connected_component'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:431:in `each_strongly_connected_component_from'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:349:in `block in each_strongly_connected_component'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:347:in `each'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:347:in `call'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:347:in `each_strongly_connected_component'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:226:in `tsort_each'
/Users/evenluo/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/tsort.rb:205:in `tsort_each'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/initializable.rb:54:in `run_initializers'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/railties-4.2.6/lib/rails/application.rb:352:in `initialize!'
/Users/evenluo/Develop/Link Firms/Apollo/config/environment.rb:5:in `<top (required)>'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:274:in `require'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:274:in `block in require'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:240:in `load_dependency'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/activesupport-4.2.6/lib/active_support/dependencies.rb:274:in `require'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/sidekiq-4.1.1/lib/sidekiq/cli.rb:233:in `boot_system'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/sidekiq-4.1.1/lib/sidekiq/cli.rb:49:in `run'
/Users/evenluo/.rvm/gems/ruby-2.3.0/gems/sidekiq-4.1.1/bin/sidekiq:12:in `<top (required)>'
/Users/evenluo/.rvm/gems/ruby-2.3.0/bin/sidekiq:22:in `load'
/Users/evenluo/.rvm/gems/ruby-2.3.0/bin/sidekiq:22:in `<main>'
/Users/evenluo/.rvm/gems/ruby-2.3.0/bin/ruby_executable_hooks:15:in `eval'
/Users/evenluo/.rvm/gems/ruby-2.3.0/bin/ruby_executable_hooks:15:in `<main>'

很明显二者的调用方的确不是同一个。但是仍然没有什么用。于是我去sidekiq提了issue: sidekiq#2942,不知道会有怎样的结果,只能自求多福等待看看吧。


后来问题得到了解决,大家可以从issue里看到这个问题的原因,大家最好还是遵从文件命名的规范呐,不然会出这种小屁问题

Image Resizing Techniques

图片尺寸调整技术

原文链接: Image Resizing Techniques

自有历史以来,iOS的开发者一直面临着一个困惑:“我要怎么调整一个图片的尺寸呢?”这个问题甚至导致了开发者与开发平台了相互不信任。网页搜索结果有数以千计,都声称自己才是真正的解决方案。

其实这个局面挺尴尬的。

本周的文章将试图基于经验深入了解各种实现方案的性能特性,而不是简单地认为谁是最佳方案。 最终在各种iOS平台上调整图片尺寸的方案中提供一个清晰的解释(对于OS X,适当地将UIImage变成NSImage)。

在继续阅读之前,请注意
在使用UIImageView时,大部分情况下手动调整图片的大小并不是很必要。我们可以通过给contentMode属性来实现大部分的需求,例举一下两个值:

  • .ScaleAspectFit 以等比例的形式将图片的frame都显示在UIImageView中。
  • .ScaleAspectFill 以等比例的形式将图片充满整个:
1
2
imageView.contentMode = .ScaleAspectFit
imageView.image = image

确定最终的尺寸

在所有工作之前,我们要先确定图片调整的目标大小。

以常量缩放

缩放一个图片最简单的方法就是设置一个常量。多数情况下,是希望通过除以一个整数来减少原来的尺寸(通过乘以整数来放大图片的需求比较少)。
单独为长宽分别计算最后得到一个新的CGSize:

1
let size = CGSizeMake(image.size.width / 2.0, image.size.height / 2.0)

或者使用一个CGAffineTransform:

1
let size = CGSizeApplyAffineTransform(image.size, CGAffineTransformMakeScale(0.5, 0.5))

等比例缩放

等比例缩放在很多场景下都非常有用。AVMakeRectWithAspectRatioInsideRect是一个AVFoundation框架中很有用的一个方法,它会帮你解决计算方面的问题

1
2
import AVFoundation
let rect = AVMakeRectWithAspectRatioInsideRect(image.size, imageView.bounds)

调整尺寸

iOS有一系列的方法来实现图片的尺寸调整,当然他们也有着不同的能力和性能特性。

UIGraphicsBeginImageContextWithOptions & UIImage -drawInRect:

这是我们在UIKit框架中能够找到的最高级别封装的API。指定一个UIImage,配合一个临时的图形上下文(graphics context)就可以用UIGraphicsBeginImageContextWithOptions()和UIGraphicsGetImageFromCurrentImageContext()来渲染一个缩放后的版本:

1
2
3
4
5
6
7
8
9
let image = UIImage(contentsOfFile: self.URL.absoluteString!)
let size = CGSizeApplyAffineTransform(image.size, CGAffineTransformMakeScale(0.5, 0.5))
let hasAlpha = false
let scale: CGFloat = 0.0 // Automatically use scale factor of main screen

UIGraphicsBeginImageContextWithOptions(size, !hasAlpha, scale)
image.drawInRect(CGRect(origin: CGPointZero, size: size))

let scaledImage = UIGraphicsGetImageFromCurrentImageContext()

UIGraphicsBeginImageContextWithOptions()创建了一个临时的渲染上下文(rendering context)到原始图片。第一个参数size是要调整到的尺寸。第二个参数isOpaque用来决定是否需要渲染alpha通道。如果将false赋给它然后作用于没有透明属性的图片可能会导致图片看起来有一种粉色色调的感觉。第三个参数scale是用于显示的缩放比例。如果设置为0.0,则会使用主屏幕的scale属性,对于Retina屏幕来说就是2.0或更高(iPhone 6 Plus则是3.0)

CGBitmapContextCreate & CGContextDrawImage

Core Graphics / Quartz 2D 提供了一个底层的API集合,它允许更多的高级设置。给定一个CGImage ,创建一个临时的位图上下文(bitmap context)用CGBitmapContextCreate()和CGBitmapContextCreateImage()来渲染缩放后的图片:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let image = UIImage(contentsOfFile: self.URL.absoluteString!).CGImage

let width = CGImageGetWidth(image) / 2.0
let height = CGImageGetHeight(image) / 2.0
let bitsPerComponent = CGImageGetBitsPerComponent(image)
let bytesPerRow = CGImageGetBytesPerRow(image)
let colorSpace = CGImageGetColorSpace(image)
let bitmapInfo = CGImageGetBitmapInfo(image)

let context = CGBitmapContextCreate(nil, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo)

CGContextSetInterpolationQuality(context, kCGInterpolationHigh)

CGContextDrawImage(context, CGRect(origin: CGPointZero, size: CGSize(width: CGFloat(width), height: CGFloat(height))), image)

let scaledImage = UIImage(CGImage: CGBitmapContextCreateImage(context))

CGBitmapContextCreate需要几个参数来构造一个上下文,它定义了需求的尺寸和每一个给定色彩空间通道的内存大小。在示例中,这些值都是从CGImage获取到的。接着,CGContextSetInterpolationQuality允许上下文插入像素以实现不同程度的保真结果。在这个情况下,’kCGInterpolationHigh’会返回最好的结果。CGContextDrawImage允许在给定的大小和位置绘制图片,允许为图片裁切特定的边,或者是符合一些特性集合,例如脸部检测等等。最后CGBitmapContextCreateImage从上下文中创建一个CGImage

CGImageSourceCreateThumbnailAtIndex

Image I/O框架很强大,但是相对比较少人了解。它独立于Core Graphics,可以在许多不同格式间读和写,获取照片元数据,执行一些常见的图片处理操作。这个框架提供了平台上最快的图片编码和解码速度,还提供高级的缓存机制,甚至能够增量地加载图片。
相比于同等效果的Core Graphics代码,CGImageSourceCreateThumbnailAtIndex提供了不同选项的简洁API

1
2
3
4
5
6
7
8
9
10
import ImageIO

if let imageSource = CGImageSourceCreateWithURL(self.URL, nil) {
let options = [
kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height) / 2.0,
kCGImageSourceCreateThumbnailFromImageIfAbsent: true
]

let scaledImage = UIImage(CGImage: CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options))
}

指定一个CGImageSource并设置选项,CGImageSourceCreateThumbnaiAtIndex就能创建了一个缩略图。尺寸调整由kCGImageSourceThumbnailMaxPixelSize决定。通过除以一个常量来以原图等比例指定一个最大的尺寸。通过指定kCGImageSourceCreateThumbnailFromImageIfAbsent或者kCGImageSourceCreateThumbnailFromImageAlways,Image I/O 能够自动为随后的调用缓存缩放结果。

用Core Image兰索斯重取样(Lanczos Resampling)

Core Image提供了一个内建的通过CILanczosScaleTranform实现的Lanczos Resampling功能。尽管是一个比UIKit更高级的API,但是Core Image中普遍使用的键值对编程使得他很笨重。
也就是说,至少模式是和其他Core Image工作流是一样的:创建一个变换滤镜,配置好,然后渲染输出一个图片。

1
2
3
4
5
6
7
8
9
10
let image = CIImage(contentsOfURL: self.URL)

let filter = CIFilter(name: "CILanczosScaleTransform")
filter.setValue(image, forKey: "inputImage")
filter.setValue(0.5, forKey: "inputScale")
filter.setValue(1.0, forKey: "inputAspectRatio")
let outputImage = filter.valueForKey("outputImage") as CIImage

let context = CIContext(options: nil)
let scaledImage = UIImage(CGImage: self.context.createCGImage(outputImage, fromRect: outputImage.extent()))

CILanczosScaleTransform接受一个inputImage, inputScale和inputAspectRatio,从变量名字上就能知道他们是干什么的。CIContext用来通过一个CGImageRef来生成UIImage,因为UIImage(CIImage:)经常不能按照期望的那样。

性能衡量

那么这些不同方法的性能如何呢?

以下是在一个运行iOS 8 GM版本的iPod Touch 5上,使用XCTestCase.measureBlock()得到了性能衡量结果:

JPEG

从NASA Visible Earth拿到一个12000*12000像素的图片,大约20MB,将其缩小到大约十分之一的大小。

Operation Time(sec) σ
UIKit 0.002 22%
Core Graphics1 0.006 9%
Image I/O2 0.001 121%
Core Image3, 4 0.011 7%

PNG

缩放Postgres.app图标至十分之一的大小,1024*1024像素,大约1MB大小。

Operation Time (sec) σ
UIKit 0.001 25%
Core Graphics5 0.005 12%
Image I/O6 0.001 82%
Core Image 0.234 43%
  • 1, 5使用了不同的CGInterpolationQuality,对于性能的影响几乎可以忽略不计。
  • 2相比同等Core Graphics函数的性能,一定程度的高度偏离的结果说明缓存缩略图的代价不菲。
  • 3创建一个CIContext是一个异常消耗资源的操作。在测试中使用的时间最多。使用一个缓存了的实例会减少平均运行时时间至UIGraphicsBeginImageContextWithOptions的水准。
  • 4, 7创建CIContext将参数kCIContextUseSoftwareRenderer设置为true使得相比于其他结果慢了许多。

结论

  • UIkit, Core Graphics 和 Image I/O 对大多数图片的缩放表现都良好
  • Core Image 在图片缩放的表现上并不佳。事实上,在官方的Core Image Programming Guide中的性能最佳实践这部分特别推荐使用Core Graphics或者Image I/O函数来裁切或事先降低取样图片。
  • 对于大部分的缩放图片,UIGraphicsBeginImageContextWithOptions应该是最佳选择。
  • 如果图片质量在考量范围之内,考虑使用CGBitmapContextCreate结合使用CGContextSetInterpolationQuality。
  • 当想缩放图片以显示缩略图的时候,CGImageSourceCreateThumbnailAtIndex提供了一个渲染和缓存的一站式解决方案

NSmutableHipster

在这里我想提醒大家,NSHipster的文章已经发布在了GitHub上,如果你想更正文章的错误或者有别的高见,请开启一个issue或是提交一个pull request。

基于Xcode 6的国际化

过去的国际化

app的国际化一直是很重要的环节,但是在Xcode 6以前,需要国际化的文件散落在不同的地方,例如Localizable.strings,InfoPlist.strings, storyboard.strings … 很多文件中,在国际化的过程中,往往是国际化团队翻译完成,然后移交给开发人员。开发人员在核对并填写入的过程中,难免有疏漏或错误,很是麻烦,直到Xcode 6

优点

  • 与开发人员分离,开发人员只需要导出与导入。
  • 对非开发人员友好,容易理解和操作。
  • 一个文件便可以解决所有的国际化工作。

准备工作

  • 确保development language是不需要翻译的语言。更改方式:进入项目的pbproject文件,然后以编辑器打开,搜索developmentRegion,把它的值改为你不需要翻译的语言,如果是简体中文就是zh-Hans,英文就是Enligsh,这里我也不是很明白,为什么其他语言使用的都是标准缩写,而英语确是用的English而不是en
  • 使用Xcode 6.3.2及以上版本。已知之前的版本在导出时做的不足够好,经常会失败。

导入与导出

在项目导航栏中点选项目,单击Editor,就可以执行导入与导出工作

其他

Xcode导出xliff文件的时候可能会遭遇各种问题,有的问题有错误信息提示有的没有,对于没有提示的错误,可以使用终端命令导出的方式,会遭遇到同样的错误,但是会有明确的提示告诉你问题出在哪里。

命令格式

1
xcodebuild -exportLocalizations -localizationPath <dirpath> -project <projectname> [[-exportLanguage <targetlanguage>]]

其中dirpath projectname targetlanguage是变量,记得去掉所有的尖括号和方括号

iOS app的默认语言

上个月尝试为团队的标准国际化进程建立一套可遵循的指导方针,然后高高兴兴上了架,但是问题来了:

我是一个韩国人,我的系统语言是韩文,为什么今天我更新app之后语言变成了中文?

当然,这是我翻译过后的用户反馈。我马上测试了一下,发现当我在选择系统语言为日文的时候,app仍然是简体中文。那么问题出在哪里呢?

在我的脑海里,一直又一个这样的假设:当所有本地化语言都与iOS系统语言不符合的时候,Base Internationlization就应该起作用了,现在看起来并不是这么一回事儿。

在上一个版本中,我有更新了两个跟语言有关的地方:一个是pbproject中的developmentRegion和项目info.Plist文件中的Localization native development region,我起初一度以为这两者是一个东西,后来发现完全不是。那么问题来了:到底是谁在作怪呢?

事实证明:developmentRegion并不会影响到app默认语言的显示,他只是影响到了Project > Info > Localizations,在导出xliff文件中才会有作用。

在我把Localization native development region从China改到United States之后,这个问题就不再存在了。非美式英文,简体中文,台湾繁体的用户将会使用英文,并且国际化文件会使用Base文件。

结论

另外值得注意的是,在用户侧,iOS保有一个叫做『首选语言顺序』的东西。它可以在设置,通用,语言和的确分类下设置。这样的一个顺序干了这么一件事情:当app并未提供当前iOS系统的语言时,iOS会对比是否提供了『首选序言序列』中的其他语言,如果仍然没有才会显示默认语言。

而这个最终的默认语言,是由Localization native development region决定的。