【八卦】关于Boot LOGO中企鹅数量与CPU核心数的关系

前段时间在 工作 玩乐 用的 MacBook Pro 上安装 Gentoo ,这本是一件枯燥的事情。由于启动时遇到内核 Panic 的问题,顺手在微博上晒图。海聊中正好 @喝雪碧的虾PeterCxy 提到 Boot Logo 里小企鹅的个数问题。

Boot Logo 是什么? 系统启动时在启动信息上方显示的图形,需要支持 fbdev 才能显示。本文最后附图中那几只可爱的小企鹅就是。

对于企鹅数量与 CPU 核心数的对应关系,当时只有一点点零星印象,随手 Google 想验证一下也没有结果。为了保持谦虚谨慎靠谱的形象,没有继续扯下去。

今天下午想到这个问题,顺手翻了一下 fbdev 相关的代码,终于找到了出处作为依据。话不多说了,上代码。

# drivers/video/fbdev/core/fbmem.c

int fb_show_logo(struct fb_info *info, int rotate)
{
    int y;

    y = fb_show_logo_line(info, rotate, fb_logo.logo, 0,
    num_online_cpus());
    y = fb_show_extra_logos(info, y, rotate);

    return y;
}

相关代码可以查看这里: http://lxr.free-electrons.com/source/drivers/video/fbdev/core/fbmem.c#L663

Tomcat中配置HTTPS证书

背景

由于移动设备经常访问连接各种不可靠的无线网络,用户密码被嗅探的风险比较大,因此对与敏感信息需要加密传输。 而 HTTPS 是一种相对成熟的方案。

使用 HTTPS 协议用于移动应用的数据传输,随着App数量越来越多而显得更强烈。

startssl.com 提供一个免费的 ssl 证书,个人测试使用应该没问题。

从 startssl 获取私钥和证书

首先在 startssl.com 注册帐号,根据提示操作,这个过程比较漫长。注意填写个人信息时要详细(至少看起来是真实的地址)。 注册的攻略在网上能看到很多。 备份个人证书,否则以后换台电脑就不能登录做管理操作了。

帐号 Ready 后,根据提示创建域名的证书,这个步骤可以得到两个文件,分别是以 .key 结尾的私钥文件和以 .crt 为结尾的证书文件。保存好这两个文件,并记住私钥文件的密码备用。

还需要另外两个文件分别是 ca.pemsub.class1.server.ca.pem。下载备用。

注:以下以域名 api.example.com 为例,实际使用请换成你自己的域名。

现在的4个文件分别为:

  • ssl.key
  • api.example.com.crt
  • ca.pem
  • sub.class1.server.ca.pem

生成 tomcat 使用的 keystore 文件

Tomcat 支持两种模式的配置方式,分别是 BIONIO 使用 JSSE 风格(使用 keystoreFile );APR/native 使用 APR 风格(使用 SSLCertificateFile / SSLCertificateKeyFile 指定私钥和证书)。

因为我们使用了 NIO ,所以按照 JSSE 风格配置,生成 keystore 文件。

首先将 key 文件和 crt 文件合并导出为 p12 。这个步骤需要输入私钥的密码,并指定一个新的导出密码。

openssl pkcs12 -export -in ../api.example.com.crt -inkey ../ssl__.key \
    -out tomcat-startssl.p12 -name api.example.com -CApath ../

然后生成 keystore 文件,需要输入上一步的导出密码,及指定新的 keystore 密码,后面几步的导入需要用到这个密码。

keytool -importkeystore -srckeystore tomcat-startssl.p12 -srcstoretype PKCS12 \
    -destkeystore startssl-api.example.com.jks

导入 startssl 的 CA

keytool -keystore startssl-api.example.com.jks -import -trustcacerts \
    -alias startcom.ca -file ../ca.pem

导入 startssl 的 sub1

keytool -keystore startssl-api.example.com.jks -import -trustcacerts \
    -alias startcom.ca.sub1 -file ../sub.class1.server.ca.pem

现在已经生成了一个可用的 startssl-api.example.com.jks

配置 tomcat

在 tomcat 的server.xml中找到相关的 Connector 部分,这部分默认已被注释掉,去掉注释并调整内容如下

<Connector SSLEnabled="true" acceptCount="100" clientAuth="false"
    disableUploadTimeout="true" enableLookups="false" maxThreads="25"
    port="8443" keystoreFile="/etc/tomcat/startssl-api.example.com.jks" keystorePass="passw0rd"
    protocol="org.apache.coyote.http11.Http11NioProtocol" scheme="https"
    secure="true" sslProtocol="TLS" />

重启 tomcat,访问 8443 端口试试。点击浏览器地址栏网址左侧的验证标志,可以检验证书的内容。

总结

通过简单的配置,将服务有 http 迁移到更安全的 https 服务。

补充1: 本文的方法因为偷懒直接使用了 startssl 来为我们管理私钥文件,从安全的角度,大部分情况下我们应该自己保管这个文件。

补充2: 现在部署 HTTPS 更好的方式是用 Nginx 做 SSL offloading ,而实际的业务服务器仍然使用 HTTP 提供服务。

补充3: startssl 个人使用没问题,如果是企业使用,建议购买有商业支持的证书。

参考文档

使用Jekyll - Data Files简介

使用 Jekyll 时,经常需要保存一些常用的数据,以便在模板中的随时调用。传统方式将数据写入 config.yml 作为 Site Variables ,会造成 config.yml 过大,而且无法将数据与配置分离,规模增长时带来管理方面的困扰。

Jekyll 最近引入 Data Files 功能,用来满足保存数据的需求。这一功能使得可以避免在模板中重复书写数据,也能避免频繁修改全局设置。插件和页面风格中也可以利用 Data Files 来保存配置。 Jekyll 支持 YAMLJSON 格式的文件,这些文件需要保存在 _data 目录下(补充: jekyll-2.1.0 开始, Data Files 支持在 _data 目录下使用子目录来保存。)。

因为 Data Files 采用 YAMLJSON 格式,所以简单易上手。

让我们来一个网页列表的例子开始认识 Data Files 的神奇之处吧。

这个网页列表中保存了一些网页的名称,地址,以及可选的备注与描述;不同类型的网页保存在各个类型的子分类节点中;分类系统支持多级结构,使用 meta: false 表示该节点不包含网址元数据,而是用于包含其它节点的分类容器。

首先初始化模板文件 links.html (只展示代码部分)。

{% include data_links.html nodes=site.data.nerd_urls %}

注意这段代码里的两个关键点:

  1. 使用 site.data.nerd_urls 读取 _data/nerd_urls.yml 文件中报错的 yaml 数据。其中 site.data. 前缀用于读取 Data File ,之后的 nerd_urls 为不含扩展名的文件名。
  2. include 另一个模板文件,并将数据使用 nodes 变量传值到模板文件中。

再来看一下 data_links.html 这个文件。

<ul>{% for node in include.nodes %}
    <li>{% if node.meta == false and node.data %}
        {{ node.name }}
        {% include data_links.html nodes=node.data %}{% else %}
        <a href="{{ node.link }}">{{ node.name }}</a>{% if node.desc %}
        {{ node.desc }}{% endif %}
    {% endif %}</li>{% endfor %}
</ul>

这里使用传入的 nodes 变量(通过 include.nodes ),递归构建一个 ul 列表。

至此,这个页面的模板部分已经完成。

现在来补充数据文件 nerd_urls.yml 。这是一个标准的 YAML 文件。

- name: News & Information*
  meta: false
  data:
    - name: WebSites
      meta: false
      data:
        - name: LWN.net
          link: http://lwn.net
        - name: Lifehacker
          link: http://lifehacker.com
        - name: Hacker News
          link: http://news.ycombinator.com
    - name: Blogs (Person)
      meta: false
      data:
        - name: Steve Souders
          link: http://stevesouders.com
        - name: Pete Keen
          link: https://www.petekeen.net
        - name: Joe Armstrong
          link: http://joearms.github.io

最终生成的页面可以在 http://blog.liulantao.com/links/ 看到。

总结:本文通过简单的例子展示了 Data Files 的使用方法。 如果你有更好的想法欢迎留言交流。

生产环境Puppet升级笔记(3.4.3→3.6.2)

线上Puppet部署时采用的是版本3.4.3,最近发现客户端经常有一些warning提示,从提示信息看出涉及到跨版本的功能变化,因此对版本进行了一个有计划的升级。

puppetlabs的最新版本为3.6.2。开始升级之前参考了官方的Release Notes(3.5,3.6)和其他人的文档(PUPPET 3.6.1 UPDATES)。

首先将yum仓库同步到yum.puppetlabs.com的最新状态,方便后续使用。

升级分为master和agent两部分,分别进行调整。

master调整

master有两个明显的变化

  1. environmentpath支持
  2. Package模块中引入的allow_virtual

environmentpath调整

关于environmentpath,配置文件改动比较小,只需修改puppet.conf中的[master]部分

+    environmentpath = $confdir/environments
-    modulepath = $confdir/modules:/usr/share/puppet/modules
-    manifestdir = $confdir/manifests

早期版本的envorinment支持通过在puppet.conf中额外的[production][testing]部分实现。需要删除puppet.conf中自定义的environment部分。

同时需要对目录进行调整,这部分工作量稍大,也在可接受的范围内。以下示例中星号(*)标记的目录对应调整即可。

调整之前目录结构为

.
├── auth.conf
├── autosign.conf
├── puppet.conf
├── manifests
│   ├── production   *
│   └── testing    *
├── modules
│   ├── production   *
│   └── testing    *
├── reports
├── ssl
├── var
└── yaml

调整之后目录结构为

.
├── auth.conf
├── autosign.conf
├── puppet.conf
├── environments
│   ├── production
│   │   ├── manifests *
│   │   └── modules   *
│   └── testing
│       ├── manifests *
│       └── modules   *
├── reports
├── ssl
├── var
└── yaml

allow_virtual调整

这是一个新增的特性,相关背景可参考PDP-897。由于默认值变化,如果不配置agent会收到一个warning信息。

修改environments/production/manifests/site.ppenvironments/testing/manifests/site.pp

+    Package {
+      allow_virtual => true,
+    }

软件包升级

通过gem升级安装puppet-3.6.2

gem install puppet --version 3.6.2

安装后重新启动puppet master即可。

agent调整

master端设置的allow_virtual是新增的一个特性,如果agent不支持,会报错,将agent的puppet版本并重启进程即可。

Error: Failed to apply catalog: Invalid parameter allow_virtual

理论上直接升级客户端puppet到3.6.2版本即可完成。不过在升级后发现使用puppet agent --genconfig生成的配置文件仍然会有warning。

Warning: Setting manifest is deprecated in puppet.conf. See http://links.puppetlabs.com/env-settings-deprecations
   (at /usr/lib/ruby/site_ruby/1.8/puppet/settings.rb:1067:in `each')
Warning: Setting modulepath is deprecated in puppet.conf. See http://links.puppetlabs.com/env-settings-deprecations
   (at /usr/lib/ruby/site_ruby/1.8/puppet/settings.rb:1067:in `each')
Warning: Setting templatedir is deprecated. See http://links.puppetlabs.com/env-settings-deprecations
   (at /usr/lib/ruby/site_ruby/1.8/puppet/settings.rb:1071:in `each')
Warning: Setting manifestdir is deprecated. See http://links.puppetlabs.com/env-settings-deprecations
   (at /usr/lib/ruby/site_ruby/1.8/puppet/settings.rb:1071:in `each')

直接根据提示取消指定的配置项即可消除警告。这里使用一个脚本进行修改。

#!/bin/bash

for s in manifest modulepath templatedir manifestdir
do
    grep "^    $s = " /etc/puppet/puppet.conf
    sed -i -e "/^    $s = /s/^/#/" /etc/puppet/puppet.conf
done

使用调整后的配置文件启动puppet agent -t,可以看到成功运行并且不再有warning。

Nginx中配置Access Control

最近处理了一个NginxACL问题,记录一下处理过程。

公司线上服务使用Nginx做前端的负载分发。对于安全原因屏蔽客户端IP的需求,在这一个层次操作,使用ngx_http_access_module提供的allow/deny语法进行配置。

# nginx.conf
events {
    use epoll;
    worker_connections  65535;
}

http {
    # ACL
    include acl.conf;

    # Vhost
    include vhosts/*.conf;
}

全局ACL配置在单独的文件acl.conf中。

# acl.conf
deny 123.45.67.89;

vhost配置文件中没有ACL配置。

# vhosts/www.example.com.conf
server {
    listen  80 default;
    server_name www.example.com;

    location / {
        root html/;
    }
}

# vhosts/api.example.com.conf
server {
    listen  80;
    server_name api.example.com;

    location / {
        proxy_pass http://backend_api;
    }
}

基于这种配置模式,每当有ACL需求时,只要更新acl.conf的ip列表即可。

问题

周一上班时,前一天值班的同事提到值班时遇到一个问题,使用deny失效了。

查看了值班的邮件记录及操作记录,是这样的一些情况:

  1. 安全组提出封禁IP需求。值班同事将涉事IP段(ip1)加入acl.conf
  2. API组提出封禁IP需求,且注明只针对api域名封禁。值班同时将设施IP端(ip2)加入vhost配置文件,如下

第2步操作如下

vhosts/api.example.com.conf
server {
    listen  80;
    server_name api.example.com;
+   deny ip2;

    location / {
        proxy_pass http://backend_api;
    }
}

在第二步操作之后,又收到安全组提供的IP段(ip3),加入acl.conf后,仍然有来自ip3的请求能获得200返回。

现在的配置情况简化表示为

http {
    deny ip1;
    server {
        server_name api.example.com;
        deny ip2;
    }
    deny ip3;
}

经过测试确认,上述配置的最终现象为server api.example.com中只有deny ip2生效,deny ip3没有生效。

追因

查看Nginx源码src/http/modules/ngx_http_access_module.c

在配置文件处理阶段有两部分需要关注。

第一个关注点 ngx_http_access_rule函数

在出现allow/deny语法时执行。

它的作用是维护每个作用域范围内的alcf->rules,这是一个ACL列表。

static char *
ngx_http_access_rule(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)

    ...

    default: /* AF_INET */

        if (alcf->rules == NULL) {
            alcf->rules = ngx_array_create(cf->pool, 4, sizeof(ngx_http_access_rule_t));
            if (alcf->rules == NULL) {
                return NGX_CONF_ERROR;
            }
        }

        rule = ngx_array_push(alcf->rules);
        if (rule == NULL) {
            return NGX_CONF_ERROR;
        }

        rule->mask = cidr.u.in.mask;
        rule->addr = cidr.u.in.addr;
        rule->deny = (value[0].data[0] == 'd') ? 1 : 0;

第二个关注点 ngx_http_access_merge_loc_conf函数

在解决嵌套定义时执行。parent代表上一级配置,child代表下一级配置。

上一级与下一级是一个相对概念,http相对server为上一级,serverhttp下一级;server相对location为上一级,locationserver下一级。

从下面代码可以看出,如果当前ACL(child->rules)为空,则继承上一级的ACL(parent->rules)。这解释了当http中定义denyserver中不定义时,http中的deny生效。

另外也证实了一个事实,即当前级别中定义过ACL之后,不会与上一级的ACL进行列表合并,只有当前列表生效。所以会出现前文提到的现象,server中定义deny后,http中的deny规则失效了。

static char *
ngx_http_access_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_access_loc_conf_t  *prev = parent;
    ngx_http_access_loc_conf_t  *conf = child;

    ...

    if (conf->rules == NULL) {
        conf->rules = prev->rules;
    }

    ...

    return NGX_CONF_OK;
}

探讨

上面ACL中,我们按先验经验想当然认为allow/deny会如其它nginx语法一样,在不同级别之间有继承关系,而事实证明这种想法是错误的。

由于不同层级之间的ACL列表独立维护,而Nginx在进行处理是只针对当前的rules遍历,一个不太严谨但是有助于理解的看法是可以认为在当前配置中增加了一个默认allow all

Apache中也有ACL相关配置,由于配置语法格式比较清晰,一般在出现嵌套时不会出现误解。

Apple Swift学习资源

最新内容请查看Wiki

苹果公司在WWDC 2014上宣布了他们将会推出一款新的编程语言,面向iOS和OS X系统的开发人员,这个新的语言被命名为Swift。

Swift在iOS 8发布的时候推向市场,用来取代现有的Objective-C语言,对于这个巨大的决定,苹果公司的解释是Swift速度更快,使用起来更加容易。在Swift推出之后,苹果公司应该也不会停止对Objective-C的支持,开发工具会同时支持两种语言。

官方文档及示例

官方文档

目前唯一的完善的文档是官方发布的《The Swift Programming Language》,是名副其实的Swift圣经。目前官方只提供iBooks版本,网友们制作了其它格式的文档可供下载。

非官方文档与社区(英文)

博客与翻译(中文)

有网友第一时间开始了官方文档的翻译工作,相信近期将由更多文档和教程出现。

社区

评论

翻译

课程

官方示例

Apple同时发布了3个示例程序,用于初窥Swift开发的项目。

  • Lister: A Productivity App Built in Swift apple / github

  • UICatalog: Creating and Customizing UIKit Controls in Swift apple / github

  • Adventure: Building a SpriteKit Game Using Swift apple / github

  • GestureRecognizers: Using standard UIGestureRecognizers in Swift apple / github

非官方代码分享

开发工具

xcode 6 beta 下载

xcode 6 beta安装系统要求为MacOSX 10.9.3+

其它

因为重名躺枪的Swift

  • Swift Lang 一门很专业的并行编程语言,有苹果在Swift页面的链接,肯定带过去很多访问量。
  • OpenStack Swift OpenStack Object Storage (Swift)。
  • Swift聊天工具 基于XMPP的聊天工具及服务端SDK。

  • Taylor Swift 美国乡村音乐女創作歌手、吉他歌手、演员。这位1989年出生的美女获得过数不清的格莱美奖及其它排行榜大奖。2014/05/30刚举办了泰勒•斯威夫特“红”巡演上海演唱会。WWDC2014之后三天,她从Google搜索结果首页被挤出,很受伤,歌迷们也很受伤。去脸盆网关注她,去音悦台听她的歌

讨论区

  • 【iOS开发者-开始Swift】QQ交流群32958950 申请时请说明身份。

[CVE-2014-0196]Kernel本地提权漏洞

描述

SUSE社区在2014年4月29日发现一个pty设备的race condition,可导致内存泄漏,从而可以用于本地提权。此漏洞被报告到上游kernel security邮件组,已经被证实并修复。

官方描述为:

A flaw was discovered in the Linux kernel's pseudo tty (pty) device.
An unprivileged user could exploit this flaw to cause a denial
of service (system crash) or potentially gain administrator privileges.

SUSE社区的bugzilla中有POC代码用于针对特定版本的内核进行验证,可能需要针对特定版本进行修改才能运行。

影响范围及修复

kernel官方评估范围为内核版本2.6.31-rc33.15-rc4

Note that this is nicely reproducible by an ordinary user using
forkpty and some setup around that (raw termios + ECHO). And it is
present in kernels at least after commit
d945cb9cce20ac7143c2de8d88b187f62db99bdc (pty: Rework the pty layer to
use the normal buffering logic) in 2.6.31-rc3.

RedHat企业版(RHEL)版本5系列不受影响(因为默认内核是较旧的2.6.18 ),RHEL版本6正在准备相关更新,Fedora的更新已经发布。Ubuntu和Debian已经发布内核更新。

建议涉及相关版本的用户关注相应发行版的升级信息,以避免造成损失。

内核维护者已经提交了修复patch

Diffstat
-rw-r--r--  drivers/tty/n_tty.c 4   
1 files changed, 4 insertions, 0 deletions
diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c
index 41fe8a0..fe9d129 100644
--- a/drivers/tty/n_tty.c
+++ b/drivers/tty/n_tty.c
@@ -2353,8 +2353,12 @@ static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,
            if (tty->ops->flush_chars)
                tty->ops->flush_chars(tty);
        } else {
+           struct n_tty_data *ldata = tty->disc_data;
+
            while (nr > 0) {
+               mutex_lock(&ldata->output_lock);
                c = tty->ops->write(tty, b, nr);
+               mutex_unlock(&ldata->output_lock);
                if (c < 0) {
                    retval = c;
                    goto break_out;

参考文档

使用软件包神器fpm将Python包转为RPM包

之前的一篇文章中介绍过使用fpm制作rpm包,相信实践过的同学已经见识过fpm的威力。

作为软件包管理工具,fpm还可以实现不同软件包类型之间的相互转换。本文将简单演示一下软件包转换的功能。

文中用例来自于日常工作中的实际需求,需要在系统中安装Scrapy工具。写本文时scrapy的最新版本为0.22。不过业务指定的版本为0.16。

下面我们看一下软件包准备的过程。

转换第一个python包

首先制作python-scrapy包。

fpm -s python -t rpm scrapy==0.16.5

说明:这里我们用到了-s参数,指定源格式。指定为python包时,fpm将使用easy_install的python源获取源文件。通过“==”来指定scrapy版本号,这与easy_install的写法完全一致。

命令执行完毕,可以看到当前目录生成文件python-scrapy-0.16.5-1.noarch.rpm

解决依赖

使用yum命令测试安装。

yum localinstall python-scrapy-0.16.5-1.noarch.rpm

从输出看出缺少依赖的python-w3lib包。

Error: Package: python-scrapy-0.16.5-1.noarch (/python-scrapy-0.16.5-1.noarch)
       Requires: python-w3lib >= 1.2

使用同样的方式创建这个包。

fpm -s python -t rpm w3lib==1.2

“影子”包

继续使用yum命令测试安装,发现另一个依赖(python-pyopenssl)。

yum localinstall python-scrapy-0.16.5-1.noarch.rpm python-w3lib-1.2-1.noarch.rpm

Error: Package: python-scrapy-0.16.5-1.noarch (/python-scrapy-0.16.5-1.noarch)
       Requires: python-pyopenssl

在yum仓库base中,可以搜索到pyOpenSSL.x86_64这个包,因此可以利用已有的包,避免重复创建以及可能的文件冲突。这里创建一个叫做python-pyopenssl的“影子”包,通过依赖包的方式引入pyOpenSSL。

fpm -s empty -t rpm -n python-pyopenssl -v 0.10 -d 'pyOpenSSL >= 0.10'

创建的方式与上面的有些差异。最主要的一项,将源格式指定为empty,意味着这个不包含文件。通过-d参数指定将引入的依赖包名及版本。这个rpm仅仅表示一个依赖关系。

现在重新测试一下

yum localinstall python-scrapy-0.16.5-1.noarch.rpm python-w3lib-1.2-1.noarch.rpm python-pyopenssl-0.10-1.x86_64.rpm

从输出可以看到已经能完成依赖检查,引入了pyOpenSSL包,可以进行安装。 将生成的3个rpm包放入yum仓库,方便部署系统使用。

总结

本文以python包转换为rpm的例子,简要演示了fpm进行package格式转换的功能。同时兼顾利用“影子”包的方式来解决仓库中已有软件包但是不同名的问题。

5分钟开发iOS应用-使用RubyMotion

背景

这是本人在iOS/RubyMotion开发方面的第一篇文档,作为对相关工具链的经验记录。

我对各种新奇技术一直保持一定的兴趣,而不仅局限于工作相关的领域。移动领域在很大程度上影响了人们生活的习惯,从2012开始关注iOS开发,并且玩票做过一个简单的工具应用

2013年发现了RubyMotion这个神器并持续关注,看到RM的工具链逐渐走向成熟的过程,社区也逐步壮大,目前基本进入了一个完善且稳定发展的状态。

关于RubyMotion(RM)

优势

RubyMotion是一个能帮助你使用Ruby语言来替代Objective-C来开发iOS平台及OSX平台应用的工具。Ruby语言相对与Objective-C的优势是较少的代码量键入,以及动态类型系统。 如:

UILabel *label = [[UILabel alloc] init];

完全等价于

label = UILabel.alloc.init

经过RubyMotion改进后,可以使用与Ruby常用方式相同的写法:

label = UILabel.new

底层实现

这个工具在语言层面与MacRuby一脉相承,通过将Ruby代码编译为与Objective-C相同的LLVM底层代码,实现了与Objective-C/Cocoa框架的交互与统一。Cocoa的类与Ruby的类实现了对应,代码中消息传递的目标仍然是Objective-C的对象,Apple官方文档的实例代码可以经过简单的变换即可直接使用。

采用这种方式的另一个好处是不需要维护庞大的基础代码,可以支持Apple SDK的升级。

开发环境准备

开发iOS App的传统工具基本都包含在Xcode套件中,包含编译器,代码编辑工具,界面工具,iOS模拟器,发布工具等。理论上使用RubyMotion只需要有编译器和模拟器即可进行开发阶段的工作。考虑到采用Xcode中优秀的工具如Interface Builder,更好的选择是仍然安装完整Xcode套件。

Ruby运行环境。RubyMotion用到了rake等工具,推荐使用rvm来安装ruby环境,建议安装ruby-2.0.0之后的版本。(如果你用过cocoapods,可能已经安装过了)。

\curl -sSL https://get.rvm.io | bash -s stable
source /etc/profile

rvm install ruby-2

在RubyMotion官网购买后,你会收到证书和安装文件的下载链接。按提示安装后,可以在Shell中使用motion命令。

之后大部分操作都在shell中使用motion命令及rake命令。

开始代码相关

初识motion命令

执行一条命令即可创建一个工程的代码框架。

$ motion create HelloWorld
Create HelloWorld
Create HelloWorld/.gitignore
Create HelloWorld/app/app_delegate.rb
Create HelloWorld/Gemfile
Create HelloWorld/Rakefile
Create HelloWorld/resources/Default-568h@2x.png
Create HelloWorld/spec/main_spec.rb

创建的文件能够在输出结果看到。

进入代码目录,初始化ruby工具:

➜  ~ $  cd HelloWorld
➜  HelloWorld $  bundle
Fetching gem metadata from https://rubygems.org/..
Resolving dependencies...
Using rake (10.3.1) 
Using bundler (1.3.5) 
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.

运行这个项目

➜  HelloWorld  rake
     Build ./build/iPhoneSimulator-7.1-Development
   Compile ./app/app_delegate.rb
    Create ./build/iPhoneSimulator-7.1-Development/HelloWorld.app
      Link ./build/iPhoneSimulator-7.1-Development/HelloWorld.app/HelloWorld
    Create ./build/iPhoneSimulator-7.1-Development/HelloWorld.app/PkgInfo
    Create ./build/iPhoneSimulator-7.1-Development/HelloWorld.app/Info.plist
      Copy ./resources/Default-568h@2x.png
    Create ./build/iPhoneSimulator-7.1-Development/HelloWorld.dSYM
  Simulate ./build/iPhoneSimulator-7.1-Development/HelloWorld.app
(main)>    

第一次运行会编译项目的代码,需要等待一段时间。之后能看到iOS模拟器启动。

这时候的应用只是一个黑色的屏幕,没有任何文字说明和页面提示。Shell此时进入交互模式

怎么样,这种感觉,有没有想到初恋的Rails?

Say “Hello World!”

注:本节参考RubyMotion Tutorial的例子

修改./app/app_delegate.rb,修改后的相关内容如下。

class AppDelegate
  def application(application, didFinishLaunchingWithOptions:launchOptions)
    alert = UIAlertView.new
    alert.message = "Hello World!"
    alert.show

    puts "Hello World, Again!"

    true
  end
end

App初始化时创建一个UIAlertView对象,之后显示出来。

重新执行rake命令,这次可以看到界面中间的提示消息。

RubyMotion Hello World

从交互窗口可以看到输出的消息Hello World, Again!

➜  HelloWorld  rake
     Build ./build/iPhoneSimulator-7.1-Development
    Create ./build/iPhoneSimulator-7.1-Development/HelloWorld.app/Info.plist
  Simulate ./build/iPhoneSimulator-7.1-Development/HelloWorld.app
(main)> Hello World, Again!
(main)> 

这个例子使用了Cocoa的功能(UIAlertView),实现了基本的文字展示,并且还有控制台的文本输出——使用puts进行基本的辅助调试。

既然是Hello World,细节就不解释太多了。以后的文章会关注更多方面。

总结

RubyMotion已经形成一个基本的生态圈,进行App开发的上手速度也远远超过了传统工具;配合Xcode工具链使用,大大提高开发速度,能改善代码可维护性。

Reference

  1. RubyMotion Official
  2. RubyMotion Tutorial
  3. RubyMotion on Quora

使用InstantClick提升链接打开速度

博客页面总是慢吞吞,这样的体验大概是每个博客作者都不愿意看到的。

最近有一个叫做InstantClick的项目,利用鼠标点击链接前的短暂时间进行预加载,从而在感观上实现了迅速打开页面的效果。

InstantClick原理

What is it

InstantClick是一个能神奇的改善网站浏览速度的JavaScript库。

Why

尽管网络带宽已经有很大增加,网站并没有变得更快,这是因为加载网页时的最大平均是网络延时。而延时是一个不可避免的物理限制,因此InstantClick使用了预加载的方式来取巧达到加速目的。

在访问者点击一个链接之前,鼠标会悬停在链接上面。悬停(mouseover)或按下(mousedown)与点击(click,按下并松开鼠标)事件之间通常有200ms~300ms的间隔,InstantClick 利用这个时间间隔预加载页面。这样当你打开页面的时候,其实页面已经加载到本地了,也就会很快能个完成渲染。

设置方法(以octopress为例)

下载instantclick.js

InstantClick下载页面下载最小化的js文件,只有1.6kb。

url [http://instantclick.io/instantclick.min.js]

放到octopress的_source/javascripts/目录中

curl http://instantclick.io/instantclick.min.js -o octopress的_source/javascripts/instantclick.min.js

初始化

在布局文件载入js,并初始化。(本例通过whitelist模式初始化,既默认不对链接开启预加载,需要对每个链接指定,防止产生多余的请求)。

修改source/_includes/custom/after_footer.html

<script src="/javascripts/instantclick.min.js"></script>
<script data-no-instant>InstantClick.init(true);</script>

针对每类链接开启预加载

a标签中增加data-instant属性,即可开启预加载。

如导航栏的链接:

<li><a href="/blog/archives">Archives</a></li>
<li><a href="/about">About me</a></li>

修改为

<li><a href="/blog/archives" data-instant>Archives</a></li>
<li><a href="/about" data-instant>About me</a></li>

总结

单纯提高网络速度难以快速提升性能时,使用预加载提升体验已经是一个普遍采用的方式。

社交网站可以采用预加载技术将热门图片预先加载到用户本地,新闻网站可以预先读取新闻内容,通过这种方式来避免引起用户等待过久而砸显示器的冲动。