龍昌博客

从Pythoneer转向Rubist

Xcode文档离线安装

| Comments

在 Xcode 中下载安装文档速度太慢了,不得已只得自行下载,然后再手动安装。

  1. 首先在 https://developer.apple.com/library/downloads/docset-index.dvtdownloadableindex 找到需要下载的文档的下载地址。

这里我需要下载的是 iOS 9.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
<!-- START iOS doc set -->
<dict>
  <key>fileSize</key>
  <integer>1071665431</integer>
  <key>identifier</key>
  <string>com.apple.adc.documentation.iOS</string>
  <key>name</key>
  <string>iOS 9.2 Documentation</string>
  <key>source</key>
  <string>https://devimages.apple.com.edgekey.net/docsets/20151208/031-43202-A.dmg</string>
  <key>userInfo</key>
  <dict>
    <key>ActivationPredicate</key>
    <string>$XCODE_VERSION >= '7.2'</string>
    <key>Category</key>
    <string>Documentation</string>
    <key>IconType</key>
    <string>IDEDownloadablesTypeDocSet</string>
    <key>InstallPrefix</key>
    <string>$(HOME)/Library/Developer/Shared/Documentation/DocSets</string>
    <key>InstalledIfAllReceiptsArePresentOrNewer</key>
    <dict>
      <key>com.apple.pkg.7.0.iOSDocset</key>
      <string>10.9.0.0.1.1449203766</string>
    </dict>
    <key>RequiresADCAuthentication</key>
    <false/>
    <key>Summary</key>
    <string>My description of content</string>
  </dict>
  <key>version</key>
  <string>92.7</string>
</dict>
<!-- END iOS doc set -->

下载地址为: https://devimages.apple.com.edgekey.net/docsets/20151208/031-43202-A.dmg

如果觉得官网下载速度太慢了,也可以从我的百度网盘下载: http://pan.baidu.com/s/1pKsmkY3 。下载完成之后自行进行文件合并、md5校验。

  1. 将下载的 dmg 文件移动到 ~/Library/Caches/com.apple.dt.Xcode/Downloads/ 目录下(如果目录不存在,自行创建), 并重命名为 <identifier>-<version>.dmg 这样的形式,在这里为: com.apple.adc.documentation.iOS-92.7.dmg

然后转到文档所在目录: ~/Library/Developer/Shared/Documentation/DocSets,如果对应的文档文件已存在则删除。 rm -rf com.apple.adc.documentation.iOS.docset

  1. 打开 Xcode ,点击下载对应的文档。此时应该会跳过下载步骤而直接进行安装。

Xcode

Javascript使用async进行流程控制

| Comments

前言

突然发现博客好久没有更新了,主要是因为最近这几个月比较忙。之前由 Python 转向了 Ruby,现在又由后端转向了前端。 这几个月接触的内容略有点多,信息量有点大,主要都是 js 相关的。准备之后抽时间将这些知识整理整理,沉淀沉淀。

Async

由于 js 是异步的,之前在使用 loopback 进行 server 端开发时,很容易就出现了比较深层次的回调嵌套。 async.js是 js 的一个工具,可以用来方便的控制 js 中的异步流程的,类似的库还有 Promise、RxJS 等。 最初它是设计用于 nodejs 的,不过在浏览器端也可以使用。

安装

安装方法很简单,直接使用 npm 即可: npm install async

使用方法

首先是加载 async 库,在 server 端使用 var async = require('async');, 在浏览器端直接引用即可: <script type="text/javascript" src="async.js"></script>

async 提供一些集合操作方法和流程控制方法,我比较常用的是:eachmapserieswaterfall 这些方法。 其中 eachmap 方法与 lodash 的类似,可以用来遍历某个集合并执行一些操作。

each 方法定义如下:

1
async.each(collection, iterator, [callback])

该方法会对 collection 每个元素都调用 iterator 操作, iterator 函数原型为: iterator(item, callback)。 当 collection 中的所有元素遍历完成或者执行 iterator 时发生错误就会调用 callback 回调,原型为: callback(err)

each 方法只是对每个元素进行操作,如果还需要获取操作的结果,那么可以使用 map 方法。定义如下:

1
async.map(collection, iterator, [callback])

mapeach 类似,只是 callback 定义为: callback(err, results)resultsiterator 操作的结果集合。

如下是一个例子,一次读取多个文件的内容:

1
2
3
async.map(['file1','file2','file3'], fs.readFile, function(err, results){
    // doSomething();
});

seriesmap 类似,不过 series 是遍历一个方法合集并挨个执行,然后返回结果:

1
async.series(tasks, [callback])

如:

1
2
3
4
5
6
7
8
9
10
11
async.series([
  function fun1(callback){
    callback(null, 'one');
  },
  function fun2(callback){
    callback(null, 'two');
  }
],
function(err, results){
  // doSomething();
});

注意,以上这些方法各个任务的完成时间顺序是不确定的。如果有一些操作是需要按照先后顺序执行,可以使用 waterfall

1
async.waterfall(tasks, [callback])

例如在 loopback 的一个 controller 中,提供修改用户密码的功能。原始写法如下:

1
2
3
4
5
6
7
8
9
10
router.post('/user/password', function(req, res) {
  User.findById(req.body.id, function(err, user){
    if (err) doSomething();
    user.password = req.body.password;
    user.save(function(err){
      if (err) doSomething();
      res.status(200).end();
    });
  });
});

上面的例子功能还比较简单,回调层级不是很深。不过如果使用 waterfall 来控制就更为简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
router.post('/user/password', function(req, res) {
  async.waterfall([
    function(callback){
      User.findById(req.body.id, callback);
    },
    function(user, callback){
      user.password = req.body.password;
      user.save(callback);
    }
  ], function(err){
    if (err) doSomething();
    res.status(200).end();
  });
});

AngularJS学习笔记7——与rails整合

| Comments

要在 rails 中使用 angular 直接在页面中引入进行即可,这个倒是不难。 只是在开发过程中突然发现了一个问题,就是 angular 的模板应该如何组织呢。 如果全写成内联模板这个不太好维护,如果是写成单个文件放在 public 目录下也不太妥。 不过好在这个问题已经有人解决了,有 angular-rails-templates 这么一个库:https://github.com/pitr/angular-rails-templates

首先安装该库:gem 'angular-rails-templates'

然后创建目录 app/assets/javascripts/templates, 并在 app/assets/javascripts/application.js 中加载对应的文件:

1
2
//= require angular-rails-templates
//= require_tree ./templates

该目录下的模板文件命名与 rails 的视图命名类似,如: foo.htmlfoo.html.erbfoo.html.haml,foo.html.slim

可以参考我的一个例子: https://github.com/wusuopu/rails-billing

这里我是使用 bower 进行安装 angular 的库,首先安装 gem 'bower-rails'

然后初始化 bower_rails: rails g bower_rails:initialize
编辑 Bowerfile,添加所需要的依赖包:

1
2
3
4
5
6
7
8
9
10
11
asset 'angular'
asset 'angular-route'
asset 'angular-resource'
asset 'angular-mocks'
asset 'angular-flash'
asset 'angular-loading-bar'
asset 'angular-flash-messages'
asset 'angular-translate'
asset 'angular-bootstrap'
asset 'bootstrap-sass-official'
asset 'components-font-awesome'

再执行命令: rake bower:install 进行安装。

接着编辑 config/initializers/assets.rb 添加配置: Rails.application.config.assets.paths << Rails.root.join("vendor","assets","bower_components")

最后加载依赖文件 app/assets/javascripts/application.js

1
2
3
4
5
6
7
8
9
//= require angular/angular
//= require angular-route/angular-route
//= require angular-resource/angular-resource
//= require angular-flash-messages/angular-flash
//= require angular-loading-bar/build/loading-bar
//= require angular-translate/angular-translate
//= require angular-bootstrap/ui-bootstrap-tpls
//= require angular-rails-templates
//= require_tree ./templates

AngularJS学习笔记6——与jQuery、Bootstrap结合

| Comments

jQuery

如果想要在 angular 内部调用 jQuery 的函数(如 jQuery 的 ajax 功能)比较简单,直接调用 $.ajax 即可。
但是如果想要在 angular 外部调用其函数就稍微麻烦一点,毕竟这也与 angular 的设计理念不符。

1
2
3
4
5
6
7
8
9
<div ng-controller="PageController">
  ...
</div>

<script>
  var appModule = angular.module('myApp', []);
  appModule.controller('PageController', ['$scope', '$http', function($scope, $http){
      ...
  }]);

例如上个这段代码,如果想要从外部访问 PageController 中的某些内容。则可以先获取 PageController 的上下文对象($scope):angular.element($('[ng-controller="PageController"]')).scope();
在外部修改了 scope 的某些值时会发现对应的视图并没有更新,这时还需要调用 scope$digest 方法进行同步,或者直接调用 $apply 方法进行操作。

Bootstrap

之前使用 angularjs 时遇到了 Bootstrap 的控件不能正常使用了,如 dropdown 组件点击了没有效果。
经过分析发现是 angularjs 将 Bootstrap 的组件的事件给截获了。

好在有 angular-bootstrap 这么一个组件可以将它们整合在一起。

http://angular-ui.github.io/bootstrap/

在Ruby中使用DATA和__END__将代码和数据混合

| Comments

之前一直不理解 __END__ 的用法,现在看了这篇文章后才算是了解了,于是便翻译之。
《Mixing code and data in Ruby with DATA and __END__》: http://blog.honeybadger.io/data-and-end-in-ruby/


你知道 Ruby 提供了一种方法在你的脚本中可以将源文件作为数据源来使用吗?当你在写一些一次性的脚本用于验证概念时这个小技巧会为你节约一些时间。让我们来看看吧。

DATA 和 __END__

在下面这个例子中,我使用了一个有趣的关键字 __END__。所有在 __END__ 下面的内容将会被 Ruby 解释器所忽略。但是有趣的是 ruby 为你提供了一个称为 DATA 的 IO 对象,就像你可以读取其他任何文件一样,它能让你读取到 __END__ 以下的所有内容。

下面这个例子中,我们遍历每一行并进行输出。

1
2
3
4
5
6
7
8
DATA.each_line do |line|
  puts line
end

__END__
Doom
Quake
Diablo

关于这个技术我最喜欢的实例是使用 DATA 来包含一个 ERB 模板。它同样也可用于 YAML、CSV等等。

1
2
3
4
5
6
7
8
require 'erb'

time = Time.now
renderer = ERB.new(DATA.read)
puts renderer.result()

__END__
The current time is <%= time %>.

实际上你也可以使用 DATA 来读取 __END__ 关键字以上的内容。那是因为 DATA 实际上是一个指向了整个源文件,并定位到 __END__ 关键字的位置。你可以试试看在输出之前将 IO 对象反转。下面这个例子将会输出整个源文件。

1
2
3
4
5
DATA.rewind
puts DATA.read # prints the entire source file

__END__
meh

多文件问题

这个技术最大的缺点是它只能用于单个文件的脚本,直接运行该文件,不能在其他文件进行导入。

下面这个例子,我们有两个文件,并且每个都有它们自己的 __END__ 部分。然而却只有一个全局 DATA 对象。因此第二个文件的 __END__ 部分刚访问不到了。

1
2
3
4
5
6
7
8
9
10
# first.rb
require "./second"

puts "First file\n----------------------"
puts DATA.read

print_second_data()

__END__
First end clause
1
2
3
4
5
6
7
8
9
10
# second.rb

def print_second_data
  puts "Second file\n----------------------"
  puts DATA.read # Won't output anything, since first.rb read the entire file
end

__END__
 
Second end clause
1
2
3
4
5
6
7
snhorne ~/tmp $ ruby first.rb
First file
----------------------
First end clause
 
Second file
----------------------

对于多文件的一个解决方案

在 Sinatra 中有一个很酷的特性是它允许你在你应用的 __END__ 部分添加多个内联模板。它看起来像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# This code is from the Sinatra docs at http://www.sinatrarb.com/intro.html
require 'sinatra'

get '/' do
  haml :index
end

__END__
 
@@ layout
%html
 
  = yield
 
@@ index
%div.title Hello world.

sinatra 是如何实现的呢?毕竟你的应用可能是运行在 rack 上。在生产环境中你不能再通过 ruby myapp.rb 来运行!他们必须有一种在多文件中使用 DATA 的解决方案。

因此如果你稍微深入一下 Sinatra 的源代码,你会发现它们并没有使用 DATA。而是使用了跟下面这段代码类似的方案。

1
2
# I'm paraphrasing. See the original at https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1284
app, data = File.read(__FILE__).split(/^__END__$/, 2)

实际上它比这个要更复杂一些,因为它们不想读取 __FILE__,它只是 sinatra/base.rb 文件。它们其实是需要获取调用了该方法的文件的内容。它们通过解析 caller 的结果来获取。

caller 方法将会告诉你当前运行的方法是从哪调用的。这里是个简单的例子:

1
2
3
4
5
def some_method
  puts caller
end

some_method # => caller.rb:5:in `<main>'

现在可以简单地获取到文件名了,然后从该文件中再提取出与 DATA 等价的内容。

1
2
3
def get_caller_data
  puts File.read(caller.first.split(":").first).split("__END__", 2).last
end

请善用它,不要作恶

希望对于这些技巧你不要经常使用。它们不会使得代码干净、可维护。

然后,你偶尔需要一些又快又脏的实现一个一次性的脚本或者验证一些概念。此时 DATA__END__ 就非常有用了。

Mac下使用supervisor进行服务管理

| Comments

最近刚切换到 Mac 平台上,感觉各种不适应。之前使用 Ubuntu 时,有 service 命令可以对服务进行管理, 但是在 Mac 系统下没有对应的工具。也许有人说可以用 launchctl 啊。但是 launchctl 的服务是开机自动启动的, 而我又不想要开机自动启动,只想在需要时启动,使用完后就停止。

由于没有相应的工具,因此我只得在终端下通过命令来启动服务,但是这个又得一直打开着一个新的终端标签。 对于有洁癖的我来说,表示很不爽。本来想自己写个脚本来管理的,但是这个又得针对每个服务写个脚本,也很麻烦。 正在纠结的时候想起了还有 supervisor 可以用。

supervisor 是使用 python 开发的一个后台服务管理程序。

首先使用 brew 安装 python 工具: brew install python,并覆盖掉系统自带的 python。 因为我有洁癖不想将软件安装在系统目录中,因此就再单独安装一个 python。 若对此不在意的可跳过此步。

然后再安装 supervisor: pip install supervisor。 supervisor 不支持 python3,并且如果你使用的是系统自带的 python ,可能需要在命令前加上 sudo。

安装完成之后默认是不会创建配置文件的,因此再手动创建配置文件 /usr/local/etc/supervisord.conf,我的配置如下:

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
[unix_http_server]
file=/tmp/supervisor.sock                                 ; path to your socket file

[supervisord]
logfile=/usr/local/var/log/supervisord/supervisord.log    ; supervisord log file
logfile_maxbytes=50MB                                     ; maximum size of logfile before rotation
logfile_backups=10                                        ; number of backed up logfiles
loglevel=error                                            ; info, debug, warn, trace
pidfile=/usr/local/var/run/supervisord.pid                ; pidfile location
nodaemon=false                                            ; run supervisord as a daemon
minfds=1024                                               ; number of startup file descriptors
minprocs=200                                              ; number of process descriptors
user=root                                                 ; default user
childlogdir=/usr/local/var/log/supervisord/               ; where child log files will live

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///tmp/supervisor.sock                     ; use a unix:// URL  for a unix socket

[program:mongod]
command=/usr/local/bin/mongod --config /usr/local/etc/mongod.conf
autostart=false
autorestart=true

[program:redis]
command=/usr/local/bin/redis-server /usr/local/etc/redis.conf
autostart=false
autorestart=true

[program:nginx]
command=/usr/local/bin/nginx -c /usr/local/etc/nginx/nginx.conf
autostart=false
autorestart=true

这里我的这几个服务都没有设置为自动启动,如有需要可自行将 autostart 设置为 true。

最后执行 supervisord 命令启动 supervisor 服务。之后就可以通过 supervisorctl 命令来进行服务管理了。

Riot.js之初体验

| Comments

Riot(http://riotjs.com/)按照官方的介绍,它是一个类似于 React 的微型框架。 压缩之后的文件只有差不多 15K 的大小,相比其他基本上都是上百K大小的框架来说确实是很微型的。

同时它的官方还给出了与 React 和 Polymer 的对比,各位感兴趣可以看看: http://riotjs.com/compare/

下面通过一个例子来体验一下。

先下载 Riot 库文件: https://raw.githubusercontent.com/riot/riot/master/riot+compiler.min.js 然后新建一个文件 index.html,内容如下:

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
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Tabs exampe</title>
    <style type="text/css" media="screen">
      .tabContent__item{
        display:none;
      }
      .tabContent__item.is-active{
        display:block;
      }
    </style>
  </head>
  <body>
    <riot-tabs></riot-tabs>

    <script src="tabs.tag" type="riot/tag"></script>
    <script src="riot+compiler.min.js"></script>
    <script type="text/javascript" charset="utf-8">
      riot.mount('riot-tabs');
    </script>
  </body>
</html>

接着再创建文件 tabs.tag

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
<riot-tabs>
  <h2>Tabs</h2>
  <ul>
    <li each={ tab, i in tabs } class="tabItem { is-active: parent.isActiveTab(tab.ref) }" onclick={ parent.toggleTab }>{tab.title}</li>
  </ul>
  <div class="tabContent">
    <div each={ tab, i in tabs } class="tabContent__item { is-active: parent.isActiveTab(tab.ref) }">{tab.content}</div>
  </div>

  this.tabs = [
    { title: 'Tab 1', ref: 'tab1', content: "(1) Lorem ipsum dolor" },
    { title: 'Tab 2', ref: 'tab2', content: "(2) Lorem ipsum dolor" },
    { title: 'Tab 3', ref: 'tab3', content: "(3) Lorem ipsum dolor" }
  ]

  this.activeTab = 'tab1'

  isActiveTab(tab) {
    return this.activeTab === tab
  }

  toggleTab(e) {
    this.activeTab = e.item.tab.ref
    return true
  }
</riot-tabs>

这个是 javascript 与 html 的混合。当然还可以使用纯 js 的写法,将 tabs.tag 改为 tabs.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
riot.tag('riot-tabs', '<h2>Tabs</h2> <ul> <li each="{ tab, i in tabs }" class="tabItem { is-active: parent.isActiveTab(tab.ref) }" onclick="{ parent.toggleTab }">{tab.title}</li> </ul> <div class="tabContent"> <div each="{ tab, i in tabs }" class="tabContent__item { is-active: parent.isActiveTab(tab.ref) }">{tab.content}</div> </div>', function(opts) {

  this.tabs = [
    { title: 'Tab 1', ref: 'tab1', content: "(1) Lorem ipsum dolor" },
    { title: 'Tab 2', ref: 'tab2', content: "(2) Lorem ipsum dolor" },
    { title: 'Tab 3', ref: 'tab3', content: "(3) Lorem ipsum dolor" }
  ];

  this.activeTab = 'tab1';

  this.isActiveTab = function(tab) {
    return this.activeTab === tab;
  }.bind(this);

  this.toggleTab = function(e) {
    this.activeTab = e.item.tab.ref;
    return true;
  }.bind(this);

});

同时将 index.html 中的 <script src="tabs.tag" type="riot/tag"></script> 修改为 <script src="tabs.js" type="riot/tag"></script>

好了,现在打开浏览器看下效果吧。

体验了一下,感觉它比 angular 之类的框架的学习成本要低。

AngularJS学习笔记5——路由

| Comments

angularjs 的 ngRoute 模块提供了前端路由的功能。 在 angularjs 1.3 中 route 模块被单独提取了出来,要使用其功能需要将引用进来。

1
2
  <script src="/lib/angular-1.3.15.min.js"></script>
  <script src="/lib/angular-route-1.3.15.min.js"></script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var routeApp = angular.module('routeApp', ['ngRoute']);
routeApp.config(['$routeProvider',function ($routeProvider) {
    $routeProvider
    .when('/list', {
      templateUrl: 'list.html',
      controller: 'RouteListCtl'
    })
    .when('/list/:id', {
        templateUrl: 'detail.html',
        controller: 'RouteDetailCtl'
    })
    .otherwise({
      redirectTo: '/list'
    });
}]);

这里定义了两条路由规则,对应的视图模板分别为 list.html 和 detail.html

视图模板有三种定义方式:
方法1:编写在单独的文件中,然后 angularjs 通过 ajax 获取其内容;
方法2:直接在页面中使用 script 标签定义,如: <script type="text/ng-template" id="list.html"> .... </script>
方法3:使用 $templateCache 进行定义,如:

1
2
3
routeApp.run(function($templateCache) {
  $templateCache.put('list.html', '...');
});

AngularJS学习笔记4——依赖注入

| Comments

依赖注入

之前有遇到过 angularjs 的代码压缩之后就报错了,通过查文档得知是因为 angularjs 是通过变量名来查找相应服务的。 而 js 代码压缩之后变量名就变化了,从而无法找到相应的服务而报错。因此就需要指明依赖服务。

1
function SomeController($scope, $http) { ... }

例如以上代码就是定义了 SomeController 这个一个控制器,并依赖 $scope$http 两个服务。 但是当该代码压缩之后,这两个参数名都变化了。因此还需要使用以下代码来指明所需的依赖。

1
SomeController.$inject = ['$scope', '$http'];

或者另一个写法:

1
2
3
4
angular.module('app', [])
       .controller('SomeController', ['$scope', '$http', function($scope, $http){

}]);

这样就可以保证我们的代码压缩之后也能正常运行了。

自定义服务

上面的 $http 服务为我们提供了 ajax 的功能。

需要自定义服务的可以使用 module.factory 方法或者 module.service 方法进行注册服务。 这两个方法稍微有些不同,一般我习惯使用 module.factory 。 然后在需要使用自定义服务的地方,将其添加到依赖列表中即可。

AngularJS学习笔记3——表单

| Comments

双向绑定

使用 ngModel 指令将输入框与 model 进行绑定,如: <input type="text" ng-model="text" name="text" />
当输入框或者 model 的值其中一方改变都会影响另一方。

表单验证

之前表单验证都是用的 jquery-validation 这个插件,这需要编写大量的 js 代码用于逻辑控制, 现在改用 angularjs 之后方便了许多,不过还是要编写大量的视图代码。

一个简单的例子: https://github.com/wusuopu/angularjs-practice/blob/master/static/form/validate.html

1
2
3
4
5
6
7
8
9
<form action="#" name="user_form" ng-submit="submitForm(user_form.$valid, $event)" novalidate>

    <div class="form-group" ng-class="{'has-error' : user_form.name.$invalid && user_form.name.$dirty, 'has-success' : user_form.name.$valid}">
        <label>Name</label>
        <input type="text" name="name" class="form-control" ng-model="formData.name" required>
        <p ng-show="user_form.name.$invalid && user_form.name.$dirty" class="help-block">You name is required.</p>
    </div>
.....
</form>

首先为 form 表单设置 name 属性,并设置 novalidate 属性禁止浏览器使用 HTML5 自带的验证功能。 然后对于需要验证的字段添加相应的验证属性,angularjs 内建的验证规则如下:

  • email
  • max
  • maxlength
  • min
  • minlength
  • number
  • pattern
  • required
  • url
  • date
  • datetimelocal
  • time
  • week
  • month