普通视图

发现新文章,点击刷新页面。
昨天以前Wonderffee's Blog

解决Octopress博客访问太慢的问题

作者 wonderffee
2017年5月11日 14:54

博客好久没动了,也一直有一个问题没解决,就是打开博客太慢了。

这得从Octopress引用了Google的字体文件和jQuery文件说起,刚开始建博客就发现了这个问题。当时也解决了,使用了360推出的常用前端公共库CDN服务libs.useso.com,岂料后来360也关闭了这项服务,访问再次变慢,然后就一直没管。

今天就来解决一下吧,综合各种搜索资料,有了以下的集大成版:

1.加速jQuery访问

办法就是把jquery的cdn服务改成microsoft的。

编辑文件 source/_includes/head.html 找到下图注释掉的部分,替换成

1
2
<!--<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>-->
  <script src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.min.js"></script>

2.加速font访问

办法就是把font改成使用本地的。

新建一个目录 source/fonts,后面下载的字体就放在这个fonts文件夹中。访问PT_SansPT_Serif下载两字体对应的字体文件,分别存放于PT_Sans和PT_Serif文件夹中,各4个ttf字体。

这里有一个问题,给的链接都是Github中repo库的子文件夹链接,怎么一次性下载子文件夹呢?还真有办法,打开gitzip,在右上角文本框中粘贴子文件夹网址点Download就可以下载了,so easy!

如果不下载子文件夹,也可以安装OctoTree这个Chrome插件一个一个安装,就是稍微麻烦一些。

下载后的字体存放目录层级如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
.
├── PT_Sans
   ├── OFL.txt
   ├── PT_Sans-Web-BoldItalic.ttf
   ├── PT_Sans-Web-Bold.ttf
   ├── PT_Sans-Web-Italic.ttf
   └── PT_Sans-Web-Regular.ttf
└── PT_Serif
    ├── OFL.txt
    ├── PT_Serif-Web-BoldItalic.ttf
    ├── PT_Serif-Web-Bold.ttf
    ├── PT_Serif-Web-Italic.ttf
    └── PT_Serif-Web-Regular.ttf

接着在 source/stylesheets 下新建pt_sans.css和pt_serif.css替代原先的google fonts代码。这两个文件的内容分别是:

pt_sans.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@font-face {
  font-family: 'PT Sans';
  font-style: normal;
  font-weight: 400;
  src: local('PT Sans'), local('PTSans-Regular'), url(/fonts/PT_Sans/PT_Sans-Web-Regular.ttf) format('truetype');
}
@font-face {
  font-family: 'PT Sans';
  font-style: normal;
  font-weight: 700;
  src: local('PT Sans Bold'), local('PTSans-Bold'), url(/fonts/PT_Sans/PT_Sans-Web-Bold.ttf) format('truetype');
}
@font-face {
  font-family: 'PT Sans';
  font-style: italic;
  font-weight: 400;
  src: local('PT Sans Italic'), local('PTSans-Italic'), url(/fonts/PT_Sans/PT_Sans-Web-Italic.ttf) format('truetype');
}
@font-face {
  font-family: 'PT Sans';
  font-style: italic;
  font-weight: 700;
  src: local('PT Sans Bold Italic'), local('PTSans-BoldItalic'), url(/fonts/PT_Sans/PT_Sans-Web-BoldItalic.ttf) format('truetype');
}
pt_serif.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@font-face {
  font-family: 'PT Serif';
  font-style: normal;
  font-weight: 400;
  src: local('PT Serif'), local('PTSerif-Regular'), url(/fonts/PT_Serif/PT_Serif-Web-Regular.ttf) format('truetype');
}
@font-face {
  font-family: 'PT Serif';
  font-style: normal;
  font-weight: 700;
  src: local('PT Serif Bold'), local('PTSerif-Bold'), url(/fonts/PT_Serif/PT_Serif-Web-Bold.ttf) format('truetype');
}
@font-face {
  font-family: 'PT Serif';
  font-style: italic;
  font-weight: 400;
  src: local('PT Serif Italic'), local('PTSerif-Italic'), url(/fonts/PT_Serif/PT_Serif-Web-Italic.ttf) format('truetype');
}
@font-face {
  font-family: 'PT Serif';
  font-style: italic;
  font-weight: 700;
  src: local('PT Serif Bold Italic'), local('PTSerif-BoldItalic'), url(/fonts/PT_Serif/PT_Serif-Web-BoldItalic.ttf) format('truetype');
}

然后修改source/_includes/custom/head.html

1
2
3
<!--Fonts from Google"s Web font directory at http://google.com/webfonts -->
<link href="/stylesheets/pt_serif.css" rel="stylesheet" type="text/css">
<link href="/stylesheets/pt_sans.css" rel="stylesheet" type="text/css">

3.重新部署

重新部署自然就简单了,可以用preview先预览效果:

1
2
3
rake generate
rake preview
rake deploy

参考

Xcode自动生成版本号与根据版本号获取Git提交记录哈希值

作者 wonderffee
2015年9月24日 17:04

相信很多人都用Xcode自动生成版本号,可以省却很多麻烦。这里说的版本号是Build Number,对应plist文件中的CFBundleVersion字段。

其实我最开始看到自动生成版本号的方法是每次自动加1,当时就觉得有一个费解的问题:每修改一次版本号就要改一次plist文件,然后还要提交,团队合作时不是很麻烦而且很容易造成代码提交冲突?

直到最近我看到这篇文章,才发现了最终的解决方案:最好的 Xcode 自动生成版本号技术——不会污染Git记录,也不会造成冲突。

里面使用了下面的命令来获取Git提交次数:

1
git rev-list --all|wc -l

怎么使用就不多赘述了,看原文就是了,这里就说说原文没说到的一个问题:如何根据版本号得到对应的Git提交记录哈希值?也就是上面自动生成版本号的逆向过程。这适用于一种情况:某个版本出现了崩溃,现在想临时回退到那个崩溃版本的代码。

有人说可以在每次打包的时候打tag,可我终究还是太懒。

首先,在~/.gitconfig里加上下面的代码:

1
2
[alias]
  show-rev-number = !sh -c 'git rev-list --reverse --all | nl | awk \"{ if(\\$1 == "$0") { print \\$2 }}\"'

然后你就可以用”git show-rev-number 版本号”命令来获取版本号对应的Git commit哈希值了。

最后根据这个哈希值创建历史分支,你可以进行回退操作了:
1.git branch (自定义分支名称) (版本号) 例如:git branch s105 668f074c62406
2.git checkout (分支) 例如:git checkout s105

参考:

自动化生成APNS PEM文件

作者 wonderffee
2015年8月25日 15:23

缘由

由于工作需要,我需要大量生成iOS推送服务端所用到的PEM文件,一个个手动生成那真是要烦死。梳理了一下PEM文件的生成过程,发现涉及到证书导出、生成与验证,这样的话我还真的只能一个个手动生成。不过仔细研究,还是可以提高生成自动化水平的,比如PEM文件后期生成过程的把P12文件转换成PEM文件。

这个P12文件转换成PEM文件的自动化过程有一个难点,就是需要手动输入密码,换言之就是有交互的过程,如果写成脚本,如果让脚本自动交互呢?也就是把密码写死在脚本里,在脚本执行时自动输入密码。还真让我找到了解决这个问题的神器:expect和autoexpect。

看看Expect的介绍:我们通过Shell可以实现简单的控制流功能,如:循环、判断等。但是对于需要交互的场合则必须通过人工来干预,有时候我们可能会需要实现和交互程序如telnet服务器等进行交互的功能。而expect就使用来实现这种功能的工具。

那么autoexpect是什么呢?实际上写expect脚本仍然是挺烦的,autoexpect是用来录制生成expect脚本的,这样就更加自动化了。

实现

Mac本身带有expect工具,这可以通过expect -v看出来,但是却没有autoexpect。其实是因为Mac带的这个expect比较简单,用brew install expect就可以安装上带autoexpect的expect了。

本文中PEM文件的生成主要参考了《IOS Push 证书的重新生成》 一文,涉及到的自动化对应”把两个.p12文件转换成.pem文件”之后的过程.

在Terminal下执行autoexpect -p -f autoGeneratePEM.exp就可以开始脚本的录制了,录制成功后,生成的autoGeneratePEM.exp就是你需要的脚本了,稍做修改,就可以使用了。

下面是我录下来的脚本,由于是录制的,所以会有一些冗余代码。

如何使用:

1. 确保cert.p12和key.p12在当前目录  
2. 修改脚本中的custompassword为两个p12文件的导出密码(这里假设两个是一样的),以及修改customPemPassPhrase为你自己定义的字符串。   
3. 执行脚本时需要bundleid参数,示例:./autoGeneratePEM.exp com.test.123456,或者./autoGeneratePEM.exp 123456
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
#!/usr/bin/expect -f
#
# This Expect script was generated by autoexpect on Tue Aug 25 14:39:57 2015
# Expect and autoexpect were both written by Don Libes, NIST.


set force_conservative 0  ;# set to 1 to force conservative mode even if
            ;# script wasn't run conservatively originally
if {$force_conservative} {
  set send_slow {1 .1}
  proc send {ignore arg} {
      sleep .1
      exp_send -s -- $arg
  }
}

set timeout -1
set bundleid [lindex $argv 0]
spawn $env(SHELL)
match_max 100000
expect -exact "\[?1034hbash-3.2\$ "
send -- "openssl pkcs12 -clcerts -nokeys -out cert.pem -in cert.p12"
expect -exact "openssl pkcs12 -clcerts -nokeys -out cert.pem -in cert.p12"
send -- "\r"
expect -exact "Enter Import Password:"
send -- "custompassword\r"
expect -exact "bash-3.2\$ "
send -- "openssl pkcs12 -nocerts -out key.pem -in key.p12"
expect -exact "openssl pkcs12 -nocerts -out key.pem -in key.p12"
send -- "\r"
expect -exact "Enter Import Password:"
send -- "custompassword\r"
expect -exact "Enter PEM pass phrase:"
send -- "customPemPassPhrase\r"
expect -exact "Verifying - Enter PEM pass phrase:"
send -- "customPemPassPhrase\r"
expect -exact "bash-3.2\$ "
send -- "openssl rsa -in key.pem -out key.unencrypted.pem"
expect -exact "openssl rsa -in key.pem -out key.unencrypted.pem"
send -- "\r"
expect -exact "Enter pass phrase for key.pem:"
send -- "customPemPassPhrase\r"
expect -exact "bash-3.2\$ "
send -- "cat cert.pem key.unencrypted.pem > $bundleid.pem"
expect -exact "cat cert.pem key.unencrypted.pem > $bundleid.pem"
send -- "\r"
expect -exact "bash-3.2\$ "
send -- "exit\r"
expect eof

参考:

快速生成参考链接

作者 wonderffee
2015年6月5日 10:19

由来

秉承以前写论文加参考文献的习惯,写文章总觉得要加上参考链接比较好一些,所以现在写博客也是如此。

但是加一个参考链接好像并不是一件很容易的事情,每次的操作好像是先要复制一个标题,粘贴,再复制url,粘贴,再整理。不觉得很麻烦吗?

下面整理了一些加速这个过程的方法,由于我主要用Firefox,下面的方法都是适用于Firefox的:

生成MarkDown链接

这个很简单,安装一个Firefox插件就可以搞定了,安装这个插件就可以了:Copy as Markdown

或者,用另外一个插件:FireLink ——这个插件的功能更丰富一些

延伸一下:有没有TX是不是每天在浏览器上打开了许多Tab,关电脑时要全部关闭这些Tab,而实际上想把这些Tab保存下来?这两个插件可以部分实现这个功能:都有生成所有Tab的MarkDown链接的功能。可以写一个简单的AppleScript脚本,每天关闭时,使用插件生成所有Tab的MarkDown链接,按快捷键写入到一个以当前时间命名的文件中。

生成HTML富文本链接

在某些情况下你可能需要富文本链接,比如在Evernote中使用。 简要的转换过程是:以Google为例,根据标题“Google”和url “https://www.google.com.hk/“,得到<a href="https://www.google.com.hk/">Google</a>,再把这个HTML源码转换为HTML富文本放入剪贴板中,在Evernote中粘贴即得到Google

起初我想完全用AppleScript实现,无奈发现Firefox对AppleScript似乎做了特别处理(Chrome, Safari没有此问题,可以参考这个链接),那么只能曲线实现了——先用Firefox插件得到当前网页超链接的HTML源码,再用AppleScript生成HTML富文本放入剪贴板中——后者要得益于之前增强Mac下Evernote编辑能力的文章中的突破。

很幸运地找到了生成链接HTML源码的插件,也就是上面提到的FireLink

第一个问题解决了,然后就是用神奇的AppleScript解决第二个问题了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
on run {input, parameters}
  
  -- 必须先在Firefox中打开的网页里点右键选择Fire Link -- Html,然后再执行此脚本
  set selectedText to the clipboard
  set prefix to "<a href="
  if selectedText begins with prefix then
      set theHEX to do shell script "LC_ALL=en_US.UTF-8 pbpaste | hexdump -ve '1/1 \"%.2x\"'"
      if theHEX is "" then
          beep
      else
          run script "set the clipboard to «data HTML" & theHEX & "»"
      end if
  else
      display dialog "剪贴板中的内容不是以<a href=开头,格式不合法"
  end if
  
  return input
end run

使用MacDown的一些笔记

作者 wonderffee
2015年6月5日 09:34

最近开始用MacDown,喜欢其中的代码高亮功能,边写边预览的感觉很好,顿时觉得可以重新写MarkDown博客了。

但是发现MacDown还是有些不稳定,右栏空白,左栏空白,双栏空白,设置空白的问题都碰到了,要重启多次可能才会好。在这里找到了解决方法,但好像只能部分解决。

解决空白显示问题

Move the following files to trash:

1
2
/Users/<your_name>/Library/Preferences/com.uranusjr.macdown.LSSharedFileList.plist  
/Users/<your_name>/Library/Preferences/com.uranusjr.macdown.plist  

Empty the trash.

If you receive a message that files are busy and cannot be deleted, reboot your OS. Then empty the trash again.

Consequences

Your preferences will be reset to default; recent files list erased. No other data should be lost (this needs confirmation from @uranusjr )

命令行启动MacDown

之前用Sublime Text可以很方便地用命令行启动。MacDown当然也有,并且我发现了一个很简便的命令行启动软件的方法,应该对任何Mac软件都适用吧。

代码:

1
2
sudo echo "open -a MacDown $*" > /usr/local/bin/macdown
sudo chmod +x /usr/local/bin/macdown

方法来源于此:Added command line utility. by sorig · Pull Request #35 · uranusjr/macdown

实际使用发现用macdown命令打开文件时,并没有打开文件。vi /usr/local/bin/macdown一看,发现少了一个$*,加上就好了。

用AppleScript增强Mac下Evernote的编辑能力

作者 wonderffee
2015年6月4日 23:30

由来

上午想在Evernote中对一篇文章的子标题进行加红操作,没有格式刷只能一遍又一遍地选中文字,再打开调色板,选中颜色,关闭调色板,真是无以吐槽。

搜索了一下相关增强Evernote编辑能力的文章,发现了知乎上的一篇分享,以及v2ex上的这个帖子,都是Windows下借助Autohotkey来实现增加Evernote编辑能力的。想起以前在Windows下用Autohotkey那可真是得心应手啊,可惜Mac下没有。

想起了之前用AppleScript来实现一些快捷操作,在Mac下是否能用AppleScript脚本来简化上面繁琐的加红操作呢?

简单看了一下上面的Autohotkey脚本代码,发现只需要把选中文字转换成html格式然后粘贴就行。然而AppleScript并没有像Autohotkey那样简单的SetHTML命令。

突破

几经折腾,终于折腾出了下面的可行代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
set appName to "Evernote"

-- if application appName is running then
tell application id (id of application appName)
  activate
  tell application "System Events"
      keystroke "c" using command down
  end tell
  set selectedText to the clipboard as «class utf8»
  -- the clipboard as "HTML"
  set the clipboard to "<span style=\"color:red;\">" & selectedText & "</span>"
  set theHEX to do shell script "LC_ALL=en_US.UTF-8 pbpaste | hexdump -ve '1/1 \"%.2x\"'"
  if theHEX is "" then
      beep
  else
      run script "set the clipboard to «data HTML" & theHEX & "»"
      tell application "System Events"
          keystroke "v" using {command down}
      end tell
  end if
end tell
-- end if

原理就是复制选中的文字,加上html颜色的标签,然后变成html格式内容,粘贴即可。

比如选中的文字为“Hello”,上面的代码会先把它变成<span style="color:red;">Hello</span>,然后在Evernote中粘贴就变成了红色字体的“Hello”

其中的难点在于把html源码变成剪贴板认可的html富文本格式内容,这个借助了shell中的hexdump命令以及用剪贴板作为中介。突破了这个难点之后,你就可以任意自定义html格式了。

Automator自动化

要与Evernote集成,就要借助于Automator工具了。 Automator 是 Mac 自带的神奇小机器人,这次我们就要用它的 Workflow 功能来将脚本绑定到系统快捷键上。 先来设置 Automator,Automator 可以在 Spotlight 里快速启动。

1、菜单里选”新建“,选取文稿类型“服务”;
2、在出来的窗口右侧顶部设置“服务”收到选定的“文本”,位于”Evernote“,不要勾选”用输出内容替换选中文本“
3、在左侧选取“运行AppleScript”,双击或直接拖到右侧区域内;
4、在出现的 AppleScript 编辑窗口里输入代码,之后保存为你喜欢的名字;
5、此时在系统左上角 Automator 的下拉菜单里“服务”一栏就已经有刚才你保存的服务啦!现在点击刚刚保存的服务的名称运行一次看看~~(一定要运行一次哦)

在Automator中的脚本可以更简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
on run {input, parameters}
  -- the clipboard as "HTML"
  set the clipboard to "<span style=\"color:red;\">" & input & "</span>"
  set theHEX to do shell script "LC_ALL=en_US.UTF-8 pbpaste | hexdump -ve '1/1 \"%.2x\"'"
  
  if theHEX is "" then
      beep
  else
      run script "set the clipboard to «data HTML" & theHEX & "»"
      -- set input to the clipboard
      tell application "System Events"
          keystroke "v" using {command down}
      end tell
  end if
  
  --display dialog (the clipboard)
  -- return input
end run

保存的位置在/Users/yourusername/Library/Services/

现在开始设置快捷键。

1、点击 Automator 下拉菜单中“服务->服务偏好设置”;
2、在弹出的服务偏好设置窗口右侧,从“通用”中找到刚保存的服务名称,选中该服务,右侧可以看到“添加快捷键”的按钮;
3、点击“添加快捷键”按钮,在键盘上按下需要设置的快捷键。

我给自己自定义的快捷键是cmd+shift+1。以后要在Evernote中对选中的文字进行红色高亮时,按下cmd+shift+1就可以搞定。

是不是很简单? 这样,你就可以自定义一些自己常用的文本格式,绑定快捷键,然后就像Word中的样式表一样,一键就可以更换自己需要的格式。

这只是一个简单的例子,相信实现其它的文本格式也不是难事,权当抛砖引玉,大家应该能做出更高级的功能。

延伸——增强MarkDown编辑能力

写这篇文章时还发现另一个用途:增强MarkDown编辑能力。

众所周知,MarkDown对颜色支持似乎不是很好,如果我要把一段内容变成红色字体,办法只能是写HTML代码,可这依然是一件麻烦的事情。受上面的启发,我们可以写一个AppleScript脚本对选定的文字进行HTML包装,然后自动粘贴。那么只用一个快捷键就可以搞定了。

在Automator中只需要上面一半不到的代码就行了:

1
2
3
4
5
6
7
on run {input, parameters}
  -- the clipboard as "HTML"
  set the clipboard to "<span style=\"color:red;\">" & input & "</span>"
  tell application "System Events"
      keystroke "v" using {command down}
  end tell
end run

下面就是效果了:

同样地,我们可以为了让自己方便写MarkDown而自定义一系列样式,用AppleScript实现,形成自己的MarkDown样式表,让自己的MarkDown文章更容易地玩出更多的花样。

一些额外的发现:

  • 将文本转换成完整html源码的AppleScript脚本:
1
2
3
set customHTML to "123"
set theMsg to do shell script "echo " & quoted form of customHTML & space & "| textutil -stdin -stdout -format html -convert html -encoding UTF-8"
set the clipboard to the theMsg
  • 在terminal里用于查看刚刚复制的html内容的html源码的命令:
1
osascript -e 'the clipboard as "HTML"'|perl -ne 'print chr foreach unpack("C*",pack("H*",substr($_,11,-3)))'

利用QuincyKit + KSCrash构建自己的Crash Log收集与管理系统

作者 wonderffee
2014年7月19日 10:37

其实早就想写这篇文章了,我去年的一篇文章就提到了QunicyKit,一直拖到现在,再不写一年就过去了。

前言

我们知道,iOS bug定位是极看重crash log的,目前网上提供了不少crash log收集与管理服务,较有名的有Crashlytics, Flurry, 友盟,可能大部分人也就是使用这个。我这里要说的QuincyKit + KSCrash是一对开源组合,可能没有前者各种高大上的功能,基本功能还是有的,但更偏重于以下使用场合:

1)访问外网不太方便,或者大部分情况下在内网测试
2)对出现的crash问题要求快速响应,快速定位
3)需要自己掌控Crash Report,而不是交给别人

显而易见,第1条就足以把Crashlytics, Flurry, 友盟诸如此类的排除在外了;
关于第2条,我所知道的Flurry显示crash report延迟比较大,至少为6小时,Crashlytics稍微好一些,但是它们的服务器在国外,网页打开也比较慢。这里要额外说的是比较讨厌Crashlytics在程序每次编译时都会上传app binary与dSYM文件,在网络情况较差或app比较大的情况下相当费时。 还有我经历的另一种情况,就是开发与测试人员相隔比较远,比如开发的在5楼,测试的在1楼,在最原始的阶段测试人员发现了崩溃问题,会将测试设备送到5楼让开发人员用Mac解析,想想这效率,不言自明了吧。
第3点其实比较勉强,crash log又没多少机密可言。

QuincyKit

相信还是有一些人了解QuincyKit的,不过我看到相关的文章比较少。我之前主要参考的是Nico的《QuincyKit的crashReport框架》《炒冷饭,再提一次QuincyKit》.

QuincyKit,简而言之,是一个为iOS和Mac OS X提供的程序崩溃报告管理解决方案。关于QuincyKit的介绍大家看Nico的文章就了解得差不多了,它对测试期的应用来说确实是很方便的,不需要提前注册APP Key,不管你有多少个应用,App中集成了QuincyKit的Client端后,只管向Server端发送crash log就行,Server端会自动根据App ID来分类管理。

QuincyKit分Server端和Client端。它的Server端是用php编写的,用一个支持PHP5.2以上,还有Mysql、Apache的服务器就可以搭建起一个完整的环境,Mac/Linux/Windows系统上应该都可以完成。看似简单,实际上对从没搭建过服务器环境的初学者可能有点麻烦,不用担心,还有XAMPP这个神器来解决大部分麻烦。当初我图简单省事,就是用XAMPP来搭建基本环境的,不过相关的笔记找不到了,这里就没办法贴上了。

另外要说明一下,QuincyKit Server端的Web管理界面比较简陋,比如连按时间排序的功能都没有,不过既然是开源的,就不能要求太高,会PHP的完全可以尝试为自己订制更高级的功能。

KSCrash

这里要说的是我只使用了QuincyKit的Server端,Client端我选择了KSCrash,可能有很多人对KSCrash对比较陌生,这也不奇怪,在国内就没见到有人介绍KSCrash。为什么是KSCrash呢,个中原因,且听我慢慢道来:

1)如果我没记错的话,QuincyKit client端生成的crash报告与原生crash报告相比总是缺少最关键的那一行,而KSCrash客户端生成的crash报告会把这关键的一行放在最后一行,并提供一些额外的信息,非常有利于问题定位。大部分情况下,我只看这最后一行就能定位到问题所在。
2)QuincyKit在2013年初基本就停止更新了,而KSCrash目前仍旧是持续更新的,对于我们来说最重要是Client端的更新,比如考虑未来支持Swift的可能性。而Server端主要是用来管理crash log,免费开源的QuincyKit足够对付使用。
3)KSCrash客户端生成的crash报告在大部分情况下都不需要dSym符号文件你就可以看到函数名,问题比较明显的话很快就能得到定位。但是默认显示的行号还是不对的,如果需要具体行号,还得利用dSym符号文件解析crash报告才行。(这点似乎QuincyKit客户端也支持的)

最关键是第1点,我想会不会有人因为这放弃使用了QuincyKit。

需要注意的是,KSCrash只是一个Client端,本身是没有Server端的。但这也是它的灵活之处,因为它能对接免费的QuincyKit,收费的Hockey、Victory等Server端,也能将生成的crash log通过Email的方式发送。这只是KSCrash的特性之一,更多KSCrash的关键特性你可以看下面列举出来的,相信你能看到惊喜:

  • 支持设备上解析与离线重新解析
  • 生成的报告格式完全兼容iOS原生crash报告格式
  • 支持32位和64位模式(实验性质)
  • 能处理mach level下的错误,例如堆栈溢出
  • 侦测未能捕捉的C++异常的真实原因
  • 侦测访问僵尸(zombie or deallocated)对象的行为
  • 恢复僵尸对象或内存覆盖情况下NSException的异常信息
  • 提取对象异常调用的有效信息(比如发生unrecognized selector sent to instance 0xa26d9a0异常的情况)
  • 支持多种Server端,提供方便的API接口
  • 显示堆栈内容
  • 诊断崩溃原因
  • 记录比Apple原生crash报告更多的信息,使用JSON格式储存
  • 支持显示用户自定义的额外数据

KSCrash能处理的crash分以下几种:

  • Mach kernel 异常
  • Fatal signals
  • C++ 异常
  • Objective-C异常
  • 主线程死锁 (实验性质)
  • 自定义崩溃(脚本语言)

经过我的测试,使用KSCrash目前主要有以下两个问题:
1) 在部分情况下会造成死锁(deadlock)crash,这通常是应用在做比较耗时的操作时出现的,如果发生这样的问题,你应该尽量优化你的APP,避免耗时操作(可能是我打开了死锁检测的功能,目前这个功能是Unstable Features)
2) 如果发生crash后重启应用时收集crash报告的服务器不可达,则之前的crash报告就会被废弃,即使重新恢复网络,仍然不会重新发送。

如果有人介意这些问题,可以尝试自行修改KSCrash的开源代码达到自己的目的。

需要说明的是,App发布时,还是建议大家使用Flurry、Crashlytics或者友盟,毕竟你面对的可能是数以万计甚至更多的用户。你可以在你的应用中增加一个开关,测试期打开开关使用KSCrash上报crash log,发布前关闭开关,使用Crashlytics或其它在线crash report工具。万万要记得在自己的checklist里加上一条开关状态核对,以免忘记。

Crash Log自动解析

关于QuincyKit还有一个解析端,或者说是Mac端,因为crash log的解析是必须在Mac上进行的。要解析QuincyKit Server端收集到的crash log,就必须在解析端的Mac电脑上执行一个symbolicate.php脚本。它做的事情实际上就是将QuincyKit上未解析过的crash log下载到本地,批量进行解析,然后再批量上传回去。你可以启动一个定时任务定时去执行这个脚本,就不用每次都手动执行了。

但是有一个问题,如果你想每次都能解析成功,你的Mac上需要提前拥有与crash log对应的.app文件、.app.dSYM文件的索引。这个索引可以通过mdimport命令来实现(mdimport的介绍可参考这里)。不过肯定还是有人觉得手动执行mdimport命令进行索引挺麻烦的,最好的办法还是用脚本将各个流程串通起来自动化实现。

我能想到的一个自动化流程是:
(1) 自动构建版本,生成ipa文件和dSYM.zip文件
(2) 解析端通过脚本拿到ipa文件和dSYM.zip文件,然后copy到指定文件夹,解压,执行mdimport
(3) 在解析端的Mac电脑上开启一个定时任务,定时执行symbolicate.php

经历这3步,就可以保证你在QuincyKit web网页上永远看到的是解析后的crash log。

做到了这一切,如果你经历过手动执行symbolicatecrash命令来解析crash log的阶段,就知道一个是天上,一个是地下了。

后记

原谅我没有贴图,因为我都是在公司里搭的系统,家里没有。在网上就找到一张QuincyKit的web管理界面,供参考

搜索资料时还有意外发现,《Doutzen: Local symbolication for QuincyKit》一文的作者做了一个Mac应用来完成解析端的工作,将配置简单化,并实现了定时自动解析。不过根据评论,应该是不兼容Lion系统,估计更不会兼容最新的10.9/10.10了,好在代码是开源的,会Mac开发的稍作修改就可以拿来为己所用了。

还有人用Rails写了一个类QuincyKit的Server端,提供在线服务网站holdbug.com,但是代码好像不开源,网站也打不开了,估计是停止支持了,链接见这里

参考:

unrecognized selector sent to instance问题之诱敌深入关门打狗解决办法

作者 wonderffee
2014年5月17日 11:03

前不久在微博上看到一篇文章,《UNRECOGNIZED SELECTOR SENT TO INSTANCE 问题快速定位的方法》 其中讲了iOS unrecognized selector sent to instance问题的快速定位方法,方法是不错的,但是实际测试发现文中的方法并非万能,从我自身的经历以及文中的评论看都有不能解决的情况。

出现unrecognized selector sent to instance问题,大部分是因为对象被提前释放,指针变成野指针,还有一种情况是本身就是野指针,如声明一个局部对象,没有初始化就直接调用。定位难的原因是你知道这个野指针指到哪个类了,但是不知道是哪里产生了野指针。如果一个正常的对象调用一个不存在的方法,也会给出这个提示,不过这种情况下Xcode会直接给出crash的代码行,不存在定位难的问题。

我遇到这个问题的情况是这样的:写的代码一直在iOS7下进行调试,运行得好好的,最近想测试一下iOS6的兼容性,结果登录成功后就会产生crash,提示[NewsViewController size] unrecognized selector sent to instance,看到这个问题当时真是相当莫名其妙,NewsViewController无论如何都不可能有size这个方法,是什么让NewsViewController调用这个方法呢?

在Xcode中用size关键词搜索所有调用size的地方一个个排除?别逗了,代码里多的是。想象一下,你在iOS7下写好了全部代码,然后在iOS6下测试兼容性时出现此问题,面对茫茫如海的代码,足够让你望洋兴叹了,一个个去找,费不起那功夫。

想起来上面那篇文章中的方法,结果是毫无帮助,下断点无效。

只得再另想办法。要快速定位问题代码行,主要思路还是得下断点,还有没有别的办法下断点呢?这个时候可就要在“unrecognized selector sent to instance”的提示上做文章了,这个提示的实际意义是某个对象调用了不存在的方法。不妨逆向思考一下,既然它没有,我如果给它加上一个呢?这不下断点的机会就来了——所谓诱敌深入,关门打狗,不过如此。

于是,我就在NewsViewController中加了一个这样的方法:

1
2
3
4
- (CGSize)size{
NSLog(@"test");
return CGSizeZero;
}

在其中的NSLog行加上断点,运行工程,果然就找到了调用该方法的代码行,问题迎刃而解。

出错的代码也贴一下吧,简化一下大概就是下面这样的:

1
2
3
4
- (void)test{
UIImage *imgNormal, *imgSelected;
NSLog(@"imagNormal width is %f", imgNormal.size.width);
}

问题出在NSLog的那一行,很显然,这就是没有初始化的局部对象在实际访问时出错,系统认为它是NewsViewController对象, 不再属于UIImage类了。

需要注意的是,上面的代码你拿过去并不一定能复现同样的问题,可能就不会发生crash了。这里只是提供另一种解决思路,希望对遇到此问题的人有所帮助。

如何让朋友在你的博客上发表文章

作者 wonderffee
2014年4月16日 21:45

生命在于折腾


大家好,我是yoyowinwin.
要能在他的博客发表文章,需要他把我添加进协作者(Collaborators)里,我才能编辑。具体操作在后台。

yoyowinwin
然后呢,还需要对特定的文章指定作者名字,不然署名就会默认的取自_config.yml里的author值。
在此,我添加了author: yoyowinwin

---
layout: post
title: "如何让朋友在你的博客上发表文章"
author: yoyowinwin
date: 2014-04-16 21:45
comments: true
categories: 
---

解决IE不能上网问题小记

作者 wonderffee
2014年3月23日 17:22

虽然平常主要用Mac OS X,有时候还是不得不用XP来做一点事,却是总会遇到一些恼火的事情。比如,这一个周末的下午,都在纠缠IE不能上网,关机总是卡在“正在保存设置”,每次都得硬关机才行.

关于这个问题,我怀疑与一周前解决流氓hao123主页锁定问题有关。

XP系统在一周前被hao123恶鬼上身,主页总是被锁定为hao123的某个推广账号的网址;

搜索注册表,把所有带hao123的注册项都修改为about:blank,重启还是老样子;

不得已,祭起360清理流氓插件,无果;用360锁定主页为空白页,重启过后还是无效;

打开360上的电脑专家,搜索“主页 hao123”,找到排在第一的“IE主页被篡改为hao123”解决方案,点立即修复,hao123主页锁定的问题就解决了。

估计就是从此落下了病根,这一周电脑启动都会出现打开IE不能上网,但是chrome却能打开网页,怀疑是杀hao123病毒将某些系统关键文件给误删了还是什么的。

用360修复过上网问题,重启是没问题了,但是再重启第二次问题依旧

甚至这个不能上网问题还能导致360卡死。。。

这个IE不能上网的问题我做了各种尝试,总结起来有以下几个现象:
1. IE打不开网页,chrome上网却没有问题
2. 在IE的Internet选项中打不开连接选项卡,一点就卡死,点关闭按钮IE闪退
3. 桌面右下角系统托盘处的本地连接消失
4. 桌面“网络邻居”右键点属性,鼠标指针显示忙碌,然后什么都没有打开
5. 不能正常关机,关机时一直显示“正在保存设置”
6. 不能远程

在网上搜索怀疑是Winsock劫持问题,尝试用过下面的方法,但是也是第一次重启是变正常了,IE上网和关机都好了,第二次重启问题却依旧。

开始-运行-输入CMD-确定-输入netsh winsock reset,按Enter确定。然后重启。 如果提示需要管理员身份: 那是因为你没有管理员权限,win7系统打开C:\Windows\System32。 找到cmd.exe,右键以管理员运行就可以了,xp也是类似的方法

google了不知道多少遍都没找到一个此问题的标准答案。最后找到一个似乎解决问题的方法:重新安装TCP/IP协议,有一个简单的命令可以达到这样的效果:

1
netsh int ip reset c:resetlog.txt

执行了这个命令后,我试过重启三次托盘本地连接都能正常显示了,IE也能正常上网,但不排除是其它命令起了作用。

浪费了一个宝贵的周末下午时间,我只能说,QNMD hao123,QNMD baidu

记录下在Windows中命令行设置DNS的命令:

1
2
3
netsh
int ip
set dns name="无线网络连接" source=static addr=114.114.114.114 register=PRIMARY

capturing on interface rvi0 fails after upgrding to Mavericks

作者 wonderffee
2013年12月21日 22:27

MacBook Pro升级至Mavericks后,用Wireshark在rvi0上抓取iOS设备的流量包就出了问题,抓包不能正常显示,白茫茫的一片,没有有效抓包信息。

google了一下,有人遇到同样的问题,看来是Mavericks修改了一些东西。

找到了不完全解决办法:

1、在Wireshark的Edit菜单中打开Preferences->Protocols->DLT_USER

2、选择Edit——New——User 2(DLS=149), payload protocol项输入eth, Header size项输入108, 点击OK保存。

再重新抓包就可以了。

之所以说不完全,是因为抓包不能设置端口过滤,一旦设置端口过滤(比如增加port 8000过滤)的时候wireshark会自动退出,真是奇哉怪也。

测试了一下,是在Link-layer heaer为Pocket Tap(rvi0的默认设置)时增加端口过滤会闪退,而修改为Raw IP不会闪退,但这样修改的话又完全抓不了包,囧里个囧。。。。

实在不行可以用tcpdump抓包:

1
sudo tcpdump -i rvi0 -n -s 0 -w capture.pcap port 8000

参考:
Mavericks – can not capture from iPhone using RVI

update CocoaPods问题小记

作者 wonderffee
2013年12月21日 21:51

执行pod install时出错,提示如下:

1
The version of CocoaPods used to generate the lockfile is higher that the one of the current executable. Incompatibility issues might arise.

问题原因:CocoaPods版本低了

解决办法:执行gem update cocoapod更新CocoaPods

可以通过gem list来查看本机上所有已经安装的gem包

粗心执行了gem update,导致更新了全部的gem包,执行rake new_post[“title”]带来了新的问题:

1
2
rake aborted!
You have already activated rake 10.1.1, but your Gemfile requires rake 0.9.2.2. Using bundle exec may solve this.

问题原因:gem update更新了rake到10.1.1,而Octopress的Gemfile中要求的rake版本为0.9

解决方法:修改Octopress的Gemfile,将gem ‘rake’, ‘~> 0.9’改为gem ‘rake’, ‘~> 10.1.1’,重新执行rake new_post[“title”]就没有问题了。

20140407 CocoaPods更新慢的问题

最近可能由于出国节点的问题,无论是执行pod install还是pod update都卡在Analyzing dependencies不动了,慢到无以复加的地步,无法忍受。

其实原因在于以上两个命令执行时会升级CocoaPods的spec仓库,加一个参数可以省略这一步,然后速度就会提升不少。加参数的命令如下:

1
2
pod install --verbose --no-repo-update
pod update --verbose --no-repo-update

iOS 3D UI——CALayer的transform扩展解析

作者 wonderffee
2013年10月19日 17:44

引言

这篇文章的主要内容来自于CocoaChina论坛上的一篇文章,只不过原文在有些地方介绍得不是很详细,我这里增加了一些解析,也算是自己做笔记,原文和代码均可以在这个链接里找到:IOS 3D UI —– CALayer的transform扩展

iOS的UI是基于UIView类的,我们能看到的每个UI元素都是UIView或者UIView的子类。View按树形结构组织起来,树根是UIWindow。

UIView与CALayer

View负责界面的交互和显示,其中显示部分由CALayer来完成。每个UIView包含一个CALayer实例。可以这么认为,UIView本身是不可见的,我们能看到的都是CALayer,UIView只是负责对CALayer进行管理。

UIView的显示设置都是对CALayer属性的封装,但是这层封装掩盖了CALayer提供的3D显示功能。所以我们想让UIView显示3D的效果的话,需要直接操作CALayer。

要操作CALayer对象,首先要在工程中包含QuartzCore.framework,在文件中import <QuartzCore/QuartzCore.h>头文件。QuartzCore.framework中包含了CALayer以及CALayer一些官方子类的定义。

通过设置CALayer的transform属性,可以使CALayer产生3D空间内的平移、缩放、旋转等变化。

绕坐标轴的旋转

原始场景如图:

test

使用image.layer.transform = CATransform3DMakeRotation(M_PI/6, 0, 1, 0); 绕Y轴旋转30度后的效果:

test

可以发现,绕Y轴旋转只是在X轴上进行了缩放,这是因为,在CALayer的显示系统中,默认的相机使用正交投影,正交投影没有远小近大效果,所以在本例中,只能造成相应轴上的缩放。在这种情况下,无论是绕Y轴旋转30度还是-30度都是同样的效果。

透视投影

CALayer默认使用正交投影,因此没有远小近大效果,而且没有明确的API可以使用透视投影矩阵。所幸可以通过矩阵连乘自己构造透视投影矩阵。构造透视投影矩阵的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
CATransform3D CATransform3DMakePerspective(CGPoint center, float disZ)
{
    CATransform3D transToCenter = CATransform3DMakeTranslation(-center.x, -center.y, 0);
    CATransform3D transBack = CATransform3DMakeTranslation(center.x, center.y, 0);
    CATransform3D scale = CATransform3DIdentity;
    scale.m34 = -1.0f/disZ;
    return CATransform3DConcat(CATransform3DConcat(transToCenter, scale), transBack);
}

CATransform3D CATransform3DPerspect(CATransform3D t, CGPoint center, float disZ)
{
    return CATransform3DConcat(t, CATransform3DMakePerspective(center, disZ));
}

代码中,center指的是相机 的位置,相机的位置是相对于要进行变换的CALayer的来说的,原点是CALayer的anchorPoint在整个CALayer的位置,例如CALayer的大小是(320, 160), anchorPoint值为(0.5, 0.5),此时anchorPoint在整个CALayer中的位置就是(160, 80),正中心的位置。传入透视变换的相机位置为(0, 0),那么相机所在的位置相对于CALayer就是(160, 80)。如果希望相机在左上角,则需要传入(-160, -80)。disZ表示的是相机离z=0平面(也可以理解为屏幕)的距离。

怎么解释这段代码呢? 第一步,layer在CATransform3DPerspect方法中首先进行了t变换,要注意的是这时候进行的t变换是以anchorPoint为中心点,默认情况下是在layer的中心位置。

第二步,在CATransform3DMakePerspective方法先进行了一个(-center.x, -center.y, 0)的平移,然后进行了透视投影,最后又做了一个(center.x, center.y, 0)平移。关键在于这两个反向的平移。 当center为(0,0)也就是相机中心在CALayer的中心位置,与anchorPoint(0.5, 0.5)重合时,平移的距离为0也就是没有做平移,这时候是直接把已经做过t变换的layer通过scale进行透视投影变换的。

当center不在中心位置时,假设在CALayer的左上角,那么center为(-160, -80)。那center就平移到了(0,0)点,等于把相机点又平移回到了与anchorPoint重合的这一点,但由于平移此时这个重合点成矩形图片的左上角了。那么为什么要平移至使相机点与anchorPoint点重合呢?

这里得明确一点,相机点与layer同时在X-Y平面上做相同的偏移时,因为没有改变z值,在相机点看到的立体效果是相同的,只是相对原点的位置变动了而已。在相机点(-160, -80)看到的立体效果,就等效于在相机点(0,0)看到的把layer平移(160, 80)的立体效果.对一个layer来说,只要没有修改anchorPoint,系统所认为的内部相机点的投影是在anchorPoint这个位置,也就是相机点的(0,0)位置。因此要看到layer在相机点(-160, -80)透视投影的效果,只能先作平移变换,让相机点与layer做相同的平移使相机点移到(0,0), 完成透视投影后再平移回去。

带透视效果的绕Y轴旋转,效果如下:
test

相应的代码为:

1
2
CATransform3D rotate = CATransform3DMakeRotation(M_PI/6, 0, 1, 0);
image.layer.transform = CATransform3DPerspect(rotate, CGPointMake(0, 0), 200);

可以发现在进行逆时针旋转30度时,在中心点左侧的图离相机点比较近,呈现出了比原图大的效果,右侧的图离相机点比较远,呈现出了比原图小的效果。对比原图,图的左边界超出了屏幕,而右边界在屏幕之内,这可以通过下面的这个图来解释:
test

图中PQ为旋转后的图像在X-Z平面上的投影,相机点在O点,从O点看过过,P、Q两点在X轴上的投影为C、D点,C、D两点相对P、Q点在X轴上的原始位置都是靠左的,这也就解释了旋转后的透视投影效果左边界超出了屏幕,右边界在屏幕之内。

上图中A、B两点是O点在无穷远处所看到的P、Q两点在X轴上的投影,也就是我们在第二张图片上看到的效果。这是因为在无穷远处,观察P、Q时已经失去了近大远小的效果,所做的投影是正交投影。

如果把center设为(-160,-80),也就是把相机位置设为矩形图片的左上角,则绕Y轴旋转的透视效果如下:
test

立方体效果

CATransform3D可以使用CATransform3DConcat函数连接起来以构造更复杂的变换, 通过这些方法,可以组合出更多的效果来。下面是个翻转的动画, 使用四张同样大小的图片围成一个框,让这个框动画旋转, 形成一个立方体旋转的效果。
test

相关实现的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CATransform3D move = CATransform3DMakeTranslation(0, 0, 160);
CATransform3D back = CATransform3DMakeTranslation(0, 0, -160);

CATransform3D rotate0 = CATransform3DMakeRotation(-angle, 0, 1, 0);
CATransform3D rotate1 = CATransform3DMakeRotation(M_PI_2-angle, 0, 1, 0);
CATransform3D rotate2 = CATransform3DMakeRotation(M_PI_2*2-angle, 0, 1, 0);
CATransform3D rotate3 = CATransform3DMakeRotation(M_PI_2*3-angle, 0, 1, 0);

CATransform3D mat0 = CATransform3DConcat(CATransform3DConcat(move, rotate0), back);
CATransform3D mat1 = CATransform3DConcat(CATransform3DConcat(move, rotate1), back);
CATransform3D mat2 = CATransform3DConcat(CATransform3DConcat(move, rotate2), back);
CATransform3D mat3 = CATransform3DConcat(CATransform3DConcat(move, rotate3), back);

image0.layer.transform = CATransform3DPerspect(mat0, CGPointZero, 500);
image1.layer.transform = CATransform3DPerspect(mat1, CGPointZero, 500);
image2.layer.transform = CATransform3DPerspect(mat2, CGPointZero, 500);
image3.layer.transform = CATransform3DPerspect(mat3, CGPointZero, 500);

解析:
要形成一个立方体旋转的效果,首先需要构造出一个立方体出来,怎么构造呢?在这个例子里构造的立方体是前后左右四个面的,如果把屏幕当做立方体的“前”面,它的“左”、“后”、“右”面我们是看不见,但是这三个面可以通过“前”面旋转一个角度得到的:以立方体的中心点为支点,将“前”面分别顺时针旋转90度、180度、270。因为屏幕宽度为320,这个立方体的中心点应在屏幕中心点后方160px的地方。

现在需要解决的一个问题就是:怎么实现以立方体的中心点为支点的旋转。我们知道,在CALayer中layer的旋转是以anchorPoint为支点的,而这个anchorPoint并没有在z轴上的维度,所以修改anchorPoint是不可能的,怎么办呢?答案还是通过平移实现,虽然不能修改anchorPoint,但我们可以改变图片的位置,将图片往z轴正方向(靠近用户的方向)平移160px的距离,这时候图片与anchorPoint的相对位置,就等同于图片在原始位置与立方体中心的相对位置,它们所进行的旋转效果是相同的,只是在z轴上的绝对距离不同。旋转完成后,再平移回去,即可达到绕立方体的中心点旋转的效果。这也是变换矩阵mat0为什么要先进行z轴正方向160px平移,执行rotate0旋转之后又进行z轴负方向160px平移的缘故。

要实现旋转动画,就需要动态改变这个立方体的绕y轴的角度,在这个例子里就是添加了一个动态变化的angle达到这个目的。另外注意此例的旋转是绕y轴旋转的,根据此前一篇文章的判断方法,此时旋转的正方向应该是z轴正方向顶点指向x轴正方向顶点,从用户眼睛看来就是逆时针。如果angle是递增的,那么-angle就是递减的,因此实际看到的旋转动画会是顺时针的。

在分析这个例子时,自己又突然想到另外一个问题:对一个layer做平移,会修改它的anchorPoint和position吗?很显然,对旋转和绽放必须要有一个固定的支点,感觉上平移不需要支点也行,是不是就会修改anchorPoint呢?答案是否定时,简单做一下测试,就知道layer在做平移时,anchorPoint和position都不会改变,frame会变化,说明frame不仅受anchorPoint和position影响,还受translation影响.

参考链接

判断三维坐标系旋转正方向的简单方法

作者 wonderffee
2013年10月17日 22:26

引言

做iOS开发,不免要接触到一些特效,其中不乏3D特效,这时候就要对iOS所使用的坐标系了解才行。若不限于iOS开发,还有MacOS开发,若不知道它们所使用坐标系的不同,初学者会很容易陷于混乱,

三维坐标系

做3D特效,就要用到三维坐标系,这是后人在笛卡尔的平面坐标系的基础上发明的。三维坐标系分两种,左手坐标系和右手坐标系,为什么用左手和右手来区分呢?这是因为当确定了x轴,y轴方向之后,z轴的方向的两种,它可以通过左手或右手来确定。下面就是这两个坐标系的规则示意图(图中固定了x轴的正方向向右,y轴的正方向向上):

左手坐标系与右手坐标系

相信大多数人对图中的右手坐标系很眼熟,没错,这就是初高中数学教材用到的三维坐标系,只是我们不一定知道它叫右手坐标系。

左手坐标系我们之前很少接触,但是在计算机图形学中这种坐标系非常重要,比如iOS的UIView使用的坐标系就是左手坐标系。有人可能会说,不对吧,UIView的坐标系是原点在左上角,y轴正方向向下,图中的不是这样啊,其实没错啦,把图中的左手坐标系沿x轴旋转180度就是原点在左上角的左手坐标系,区别就是旋转的角度不同而已。这是因为左手坐标系或者右手坐标系整体旋转后性质是不变的。

对坐标系使用左手与右手的命名,有一个作用就是用来方便判断旋转的正方向,这就是左手法则和右手法则。例如对左手坐标系,确定一个旋转轴后,左手握住拳头,拇指指向旋转轴的正方向,四指弯曲的方向为旋转的正方向。相应地,右手坐标系就用右手来判定。确定了旋转的正方向后,在公式计算中就很容易知道是该使用正角度还是负角度了。下图就是右手的例子:

右手法则

但是,这个判断旋转正方向的方法还是不够快。给定任意一个旋转角度的三维坐标系,如果按上面的方法判断旋转正方向,首先,你得确定这个坐标系是左手坐标系还是右手坐标系,这时你会先拿出一只手来,像上图一样摆好三根手指的姿势来比对给定坐标系的x、y、z轴正方向看是否一致。然后根据旋转轴的正方向,用相应的手来判断旋转正方向。

其实,完全没有必要这么麻烦。怎么更方便地判断,且看我慢慢道来。

先看第一个图的两个坐标系,左边的为左手坐标系,右边的为右手坐标系,两坐标系的x轴和y轴正方向保持一致,z轴正方向相反。分别用左手法则与右手法则去判断它们各自绕z轴旋转的正方向,那么从我们眼睛看屏幕的角度来看,它们绕z轴旋转的正方向都是逆时针,这当然不会是巧合。观察这两个坐标系,就会发现这个逆时针方向与x轴正方向箭头顶点指向y轴正方向箭头顶点的方向一致,这说明绕z轴旋转的正方向与x轴正方向箭头顶端指向y轴正方向箭头顶端的方向有关联吗?我想是的。

然后再尝试判断两坐标系绕x轴旋转的正方向,它与y轴正方向顶端指向z轴正方向顶端的方向一致;而绕y轴旋转的正方向,与z轴正方向顶端指向x轴正方向顶端的方向一致。

结论一

据此,我觉得可以得出一个结论:对于任意旋转角度的三维坐标系,绕某一坐标轴旋转的正方向,与另外两个坐标轴的正方向顶端按X—>Y—>Z—>X的顺序进行指向的方向一致。

这就意味着,判断三维坐标系绕某一坐标轴旋转的正方向,不用事先知晓这个坐标系是左手坐标系还是右手坐标系,完全不需要你用手去比划.

反过来,既然判断旋转正方向这么容易,我们也可以利用它来快速判断一个坐标系是左手坐标系和右手坐标系:使用上述结论确定坐标系绕某一某旋转的正方向,然后逆用左手法则与右手法则,大拇指指向该轴的正方向,如果左手四指弯曲的方向与旋转的正方向一致,该坐标系就是左手坐标系,反之就是右手坐标系。

不过这还是复杂,还是需要用手比划。我突然想到了一个更好的方法:
想象y轴是一面墙,你面朝前方斜靠在墙上,可以假设你的头部为y轴正方向顶点,脚为x轴正方向顶点,那么z轴在你的左侧时就是左手坐标系,在右侧时就是右手坐标系。这个时候,人体的生长方向也刚好是绕z轴旋转的正方向。

结论二

再扩展一下就是:对于任意旋转角度的三维坐标系,想象你的脚踩在一个坐标轴(如x轴)正方向的顶点,头倚靠在其邻高坐标轴(如y轴)的正方向顶点,面朝背离原点的方向,那么,第三轴正方向顶点在你的左手边时,这个坐标系就是左手坐标系,在右手边时就是右手坐标系,而人体此时的生长方向就是绕第三轴(如z轴)旋转的正方向。
(注:这里的邻高坐标轴是我自己定义的一个概念,X轴的邻高坐标轴为Y轴,Y轴的邻高坐标轴为Z轴,Z轴的邻高坐标轴为X轴.)

在这个方法里,坐标系属性与绕坐标轴旋转正方向的判断达到了统一,从此可以抛弃左手法则与右手法则,也可以抛弃手指比划的方式来判断左右手坐标系,是不是会觉得很简单?

参考链接:

Mac,iOS界面中的三维坐标系

解决Octopress博客Build Failed问题

作者 wonderffee
2013年10月17日 21:11

用了一段时间的Octopress博客,感觉良好,可这毕竟是for程序员的博客,折腾免不了,这不就遇到了一次Build Failed的问题: 执行rake preview命令预览时出现了下面的提示:

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
regeneration: 1 files changed
Liquid Exception: undefined method `[]' for nil:NilClass in atom.xml
/Users/admin/iOSCode/codeGitHub/octopress/plugins/pygments_code.rb:14:in `highlight'
/Users/admin/iOSCode/codeGitHub/octopress/plugins/code_block.rb:82:in `render'
/Users/admin/.rvm/gems/ruby-1.9.3-p448/gems/liquid-2.3.0/lib/liquid/block.rb:94:in `block in render_all'
/Users/admin/.rvm/gems/ruby-1.9.3-p448/gems/liquid-2.3.0/lib/liquid/block.rb:92:in `collect'
/Users/admin/.rvm/gems/ruby-1.9.3-p448/gems/liquid-2.3.0/lib/liquid/block.rb:92:in `render_all'
/Users/admin/.rvm/gems/ruby-1.9.3-p448/gems/liquid-2.3.0/lib/liquid/block.rb:82:in `render'
/Users/admin/.rvm/gems/ruby-1.9.3-p448/gems/liquid-2.3.0/lib/liquid/template.rb:124:in `render'
/Users/admin/.rvm/gems/ruby-1.9.3-p448/gems/liquid-2.3.0/lib/liquid/template.rb:132:in `render!'
/Users/admin/.rvm/gems/ruby-1.9.3-p448/gems/jekyll-0.12.0/lib/jekyll/convertible.rb:79:in `do_layout'
/Users/admin/iOSCode/codeGitHub/octopress/plugins/post_filters.rb:167:in `do_layout'
/Users/admin/.rvm/gems/ruby-1.9.3-p448/gems/jekyll-0.12.0/lib/jekyll/page.rb:100:in `render'
/Users/admin/.rvm/gems/ruby-1.9.3-p448/gems/jekyll-0.12.0/lib/jekyll/site.rb:204:in `block in render'
/Users/admin/.rvm/gems/ruby-1.9.3-p448/gems/jekyll-0.12.0/lib/jekyll/site.rb:203:in `each'
/Users/admin/.rvm/gems/ruby-1.9.3-p448/gems/jekyll-0.12.0/lib/jekyll/site.rb:203:in `render'
/Users/admin/.rvm/gems/ruby-1.9.3-p448/gems/jekyll-0.12.0/lib/jekyll/site.rb:41:in `process'
/Users/admin/.rvm/gems/ruby-1.9.3-p448/gems/jekyll-0.12.0/bin/jekyll:253:in `block in <top (required)>'
/Users/admin/.rvm/gems/ruby-1.9.3-p448/gems/directory_watcher-1.4.1/lib/directory_watcher.rb:580:in `call'
/Users/admin/.rvm/gems/ruby-1.9.3-p448/gems/directory_watcher-1.4.1/lib/directory_watcher.rb:580:in `block in notify_observers'
/Users/admin/.rvm/gems/ruby-1.9.3-p448/gems/directory_watcher-1.4.1/lib/directory_watcher.rb:579:in `each'
/Users/admin/.rvm/gems/ruby-1.9.3-p448/gems/directory_watcher-1.4.1/lib/directory_watcher.rb:579:in `notify_observers'
/Users/admin/.rvm/gems/ruby-1.9.3-p448/gems/directory_watcher-1.4.1/lib/directory_watcher.rb:334:in `block in initialize'
/Users/admin/.rvm/gems/ruby-1.9.3-p448/gems/directory_watcher-1.4.1/lib/directory_watcher/scanner.rb:224:in `call'
/Users/admin/.rvm/gems/ruby-1.9.3-p448/gems/directory_watcher-1.4.1/lib/directory_watcher/scanner.rb:224:in `notify'
/Users/admin/.rvm/gems/ruby-1.9.3-p448/gems/directory_watcher-1.4.1/lib/directory_watcher/scanner.rb:102:in `run_once'
/Users/admin/.rvm/gems/ruby-1.9.3-p448/gems/directory_watcher-1.4.1/lib/directory_watcher/scanner.rb:150:in `run_loop'
/Users/admin/.rvm/gems/ruby-1.9.3-p448/gems/directory_watcher-1.4.1/lib/directory_watcher/scanner.rb:45:in `block in start'
Build Failed

好大一坨文字,看着就头疼,尝试rake generate也是这样的提示。。这段提示看上去像是ruby的出错堆栈信息,自己不懂ruby, 自然不知如何下手,习惯性地在google上搜索了一下,没找到答案,只好自力更生了。

堆栈信息的第一行说明了问题出现在pygments_code.rb文件中,搜索了一下pygment发现是基于python的代码高亮插件,那就说明代码高亮出了问题,最近更新的一篇文章刚好使用了代码高亮,便有思路了。

先用排除法,找到这篇文章,把其中的代码去掉,再执行rake preview预览,发现没有出现build failed问题,基本可以确定是代码导致的build failed问题。继续用排除法找到导致问题的代码,发现代码行的末尾出现了中文分号+空格,莫非是中文符号的问题?把中文分号+空格替换成英文分号,重试,问题解决!

通过实例来理解position与anchorPoint

作者 wonderffee
2013年10月14日 22:58

上一篇文章写了一些对position与anchorPoint的理解,这次就拿一些实例来加深印象。文中的例子来自别人的一篇博文,例子是不错的,但是自己刚开始也没完全搞明白,现在完全弄懂了,在这里借用一下并加以扩展,希望对看到的人有所帮助。

先把之前的结论贴出来:
1、position是layer中的anchorPoint在superLayer中的位置坐标。
2、互不影响原则:单独修改position与anchorPoint中任何一个属性都不影响另一个属性。
3、frame、position与anchorPoint有以下关系:

1
2
frame.origin.x = position.x - anchorPoint.x * bounds.size.width;
frame.origin.y = position.y - anchorPoint.y * bounds.size.height;

1.创建一个CALayer,添加到控制器的view的layer中

1
2
3
4
5
6
7
8
9
10
CALayer *myLayer = [CALayer layer];
// 设置层的宽度和高度(100x100)
myLayer.bounds = CGRectMake(0, 0, 100, 100);
// 设置层的位置
myLayer.position = CGPointMake(100, 100);
// 设置层的背景颜色:红色
myLayer.backgroundColor = [UIColor redColor].CGColor;

// 添加myLayer到控制器的view的layer中
[self.view.layer addSublayer:myLayer];

第5行设置了myLayer的position为(100, 100),又因为anchorPoint默认是(0.5, 0.5),所以最后的效果是:myLayer的中点会在父层的(100, 100)位置。

My Note:根据anchorPoint默认为(0.5, 0.5)以及结论3中的的公式,可以得到frame.origin为(50, 50),所以myLayer在图中左上角的位置应为(50, 50)。见下图:

2.若将anchorPoint改为(0, 0)

1
myLayer.anchorPoint = CGPointMake(0, 0);

MyNote:根据结论2,修改anchorPoint不影响position,则frame.origin需要重新计算,如下:

1
2
frame.origin.x = 100 - 0 * 100 = 100;
frame.origin.y = 100 - 0 * 100 = 100;

这样,myLayer的左上角就会就会移动到(100, 100)的位置。见下图:

3.若将anchorPoint改为(1, 1)

1
myLayer.anchorPoint = CGPointMake(1, 1);

MyNote:同上,直接重新计算frame.origin,会得到frame.origin为(0, 0),myLayer左上角移动到(0, 0)的位置,见图:

4.将anchorPoint改为(0, 1)

1
myLayer.anchorPoint = CGPointMake(0, 1);

MyNote:类似地,直接重新计算frame.origin,会得到frame.origin为(100, 0),myLayer左上角移动到(100, 0)的位置,见图:

5.设置frame与bounds的区别

先看代码1:

1
2
3
4
5
CALayer *myLayer1 = [CALayer layer];
myLayer1.bounds = CGRectMake(0, 0, 100, 100);
myLayer1.anchorPoint = CGPointZero;
myLayer1.backgroundColor = [UIColor redColor].CGColor;
[self.view.layer addSublayer:myLayer1];

再看代码2:

1
2
3
4
5
CALayer *myLayer2 = [CALayer layer];
myLayer2.frame = CGRectMake(0, 0, 100, 100);
myLayer2.anchorPoint = CGPointZero;
myLayer2.backgroundColor = [UIColor redColor].CGColor;
[self.view.layer addSublayer:myLayer2];

区别在于第二行代码一个使用bounds,一个使用frame, 猜猜myLayer1和myLayer2左上角的位置会相同吗?

你可能觉得是相同的,但其实不同,myLayer1左上角在(0, 0)点,myLayer1却不是预期的(0, 0)点,而是在(50, 50)点,也就是例1中的图,为什么?有人可能还会说我明明想将myLayer2左上角放在原点,怎么就不是呢?

关键在于用的是frame。对于一个还没有superLayer的layer来说,position和anchorPoint都是有默认值的,分别为(0, 0)和(0.5, 0.5),如果对该layer设置了frame,因为anchorPoint还是保持默认值不会变化,只能是position随之变动。所以根据3中的公式,position的计算如下:

1
2
position.x = 0 + 0.5 * 100 = 50
position.y = 0 + 0.5 * 100 = 50

position被确定为(50, 50),接着在代码2中又修改anchorPoint为(0, 0), 这个时候不影响position的值,只能是frame.origin被修改,也就是变成下面的:

1
2
frame.origin.x = 50 - 0 * 100 = 50;
frame.origin.y = 50 - 0 * 100 = 50;

所以对一个已经确定frame的layer来说,再修改anchorPoint就会修改layer的位置,如果只想修改anchorPoint而不改变layer的位置,就用代码1的方式来进行,或者把代码2中设置frame与anchorPoint的代码顺序调换一下,就能解决问题。

要记住的就是设置frame会隐式修改position,而默认的anchorPoint从来不会被隐式修改,只能被显式修改。

参考

彻底理解position与anchorPoint

作者 wonderffee
2013年10月13日 11:10

引言

相信初接触到CALayer的人都会遇到以下几个问题:
为什么修改anchorPoint会移动layer的位置?
CALayer的position点是哪一点呢?
anchorPoint与position有什么关系?

我也迷惑过,找过网上的教程,大部分都是复制粘贴的,有些是翻译的文章但很有问题,看得似懂非懂,还是自己写代码彻底弄懂了,做点笔记吧。

每一个UIView内部都默认关联着一个CALayer, UIView有frame、bounds和center三个属性,CALayer也有类似的属性,分别为frame、bounds、position、anchorPoint。frame和bounds比较好理解,bounds可以视为x坐标和y坐标都为0的frame,那position、anchorPoint是什么呢?先看看两者的原型,可知都是CGPoint点。

@property CGPoint position
@property CGPoint anchorPoint

anchorPoint

一般都是先介绍position,再介绍anchorPoint。我这里反过来,先来说说anchorPoint。

从一个例子开始入手吧,想象一下,把一张A4白纸用图钉订在书桌上,如果订得不是很紧的话,白纸就可以沿顺时针或逆时针方向围绕图钉旋转,这时候图钉就起着支点的作用。我们要解释的anchorPoint就相当于白纸上的图钉,它主要的作用就是用来作为变换的支点,旋转就是一种变换,类似的还有平移、缩放。

继续扩展,很明显,白纸的旋转形态随图钉的位置不同而不同,图钉订在白纸的正中间与左上角时分别造就了两种旋转形态,这是由图钉(anchorPoint)的位置决定的。如何衡量图钉(anchorPoint)在白纸中的位置呢?在iOS中,anchorPoint点的值是用一种相对bounds的比例值来确定的,在白纸的左上角、右下角,anchorPoint分为为(0,0), (1, 1),也就是说anchorPoint是在单元坐标空间(同时也是左手坐标系)中定义的。类似地,可以得出在白纸的中心点、左下角和右上角的anchorPoint为(0.5,0.5), (0,1), (1,0)。

然后再来看下面两张图,注意图中分iOS与MacOS,因为两者的坐标系不相同,iOS使用左手坐标系,坐标原点在左上角,MacOS使用右手坐标系,原点在左下角,我们看iOS部分即可。 test
图1

test
图2

像UIView有superView与subView的概念一样,CALayer也有superLayer与layer的概念,前面说到的白纸和图中的矩形可以理解为layer,书桌和图中矩形以外的坐标系可以理解成superLayer。如果各自以左上角为原点,则在图中有相对的两个坐标空间。

position

在图1中,anchorPoint有(0.5,0.5)和(0,0)两种情况,分别为矩形的中心点与原点。那么,这两个anchorPoint在superLayer中的实际位置分别为多少呢?简单计算一下就可以得到(100, 100)和(40, 60),把这两个值分别与各自的position值比较,发现完全一致,该不会是巧合?

这时候可以大胆猜测一下,position是不是就是anchorPoint在superLayer中的位置呢?答案是确定的,更确切地说,position是layer中的anchorPoint点在superLayer中的位置坐标。因此可以说, position点是相对suerLayer的,anchorPoint点是相对layer的,两者是相对不同的坐标空间的一个重合点。

再来看看position的原始定义: The layer’s position in its superlayer’s coordinate space。
中文可以理解成为position是layer相对superLayer坐标空间的位置,很显然,这里的位置是根据anchorPoint来确定的。

图2中是矩形沿不同的anchorPoint点旋转的形态,这就是类似于刚才讲的图钉订在白纸的正中间与左上角时分别造就了两种旋转形态。

anchorPoint、position、frame

anchorPoint的默认值为(0.5,0.5),也就是anchorPoint默认在layer的中心点。默认情况下,使用addSublayer函数添加layer时,如果已知layer的frame值,根据上面的结论,那么position的值便可以用下面的公式计算:

1
2
position.x = frame.origin.x + 0.5 * bounds.size.width;  
position.y = frame.origin.y + 0.5 * bounds.size.height;  

里面的0.5是因为anchorPoint取默认值,更通用的公式应该是下面的:

1
2
position.x = frame.origin.x + anchorPoint.x * bounds.size.width;  
position.y = frame.origin.y + anchorPoint.y * bounds.size.height;

下面再来看另外两个问题,如果单方面修改layer的position位置,会对anchorPoint有什么影响呢?修改anchorPoint又如何影响position呢?
根据代码测试,两者互不影响,受影响的只会是frame.origin,也就是layer坐标原点相对superLayer会有所改变。换句话说,frame.origin由position和anchorPoint共同决定,上面的公式可以变换成下面这样的:

1
2
frame.origin.x = position.x - anchorPoint.x * bounds.size.width;  
frame.origin.y = position.y - anchorPoint.y * bounds.size.height;

这就解释了为什么修改anchorPoint会移动layer,因为position不受影响,只能是frame.origin做相应的改变,因而会移动layer。

理解与运用

在Apple doc对frame的描述中有这么一句话:

Layers have an implicit frame that is a function of the position, bounds, anchorPoint, and transform properties.

可以看到我们推导的公式基本符合这段描述,只不过还缺少了transform,加上transform的话就比较复杂,这里就不展开讲了。


Apple doc中还有一句描述是这样的:

When you specify the frame of a layer, position is set relative to the anchor point. When you specify the position of the layer, bounds is set relative to the anchor point.

大意是:当你设置图层的frame属性的时候,position根据锚点(anchorPoint)的值来确定,而当你设置图层的position属性的时候,bounds会根据锚点(anchorPoint)来确定。

这段翻译的上半句根据前面的公式容易理解,后半句可能就有点令人迷惑了,当修改position时,bounds的width与height会随之修改吗?其实,position是点,bounds是矩形,根据锚点(anchorPoint)来确定的只是它们的位置,而不是内部属性。所以,上面这段英文这么翻译就容易理解了:

当你设置图层的frame属性的时候,position点的位置(也就是position坐标)根据锚点(anchorPoint)的值来确定,而当你设置图层的position属性的时候,bounds的位置(也就是frame的orgin坐标)会根据锚点(anchorPoint)来确定。

在实际情况中,可能还有这样一种需求,我需要修改anchorPoint,但又不想要移动layer也就是不想修改frame.origin,那么根据前面的公式,就需要position做相应地修改。简单地推导,可以得到下面的公式:

1
2
positionNew.x = positionOld.x + (anchorPointNew.x - anchorPointOld.x)  * bounds.size.width  
positionNew.y = positionOld.y + (anchorPointNew.y - anchorPointOld.y)  * bounds.size.height

但是在实际使用没必要这么麻烦。修改anchorPoint而不想移动layer,在修改anchorPoint后再重新设置一遍frame就可以达到目的,这时position就会自动进行相应的改变。写成函数就是下面这样的:

1
2
3
4
5
- (void) setAnchorPoint:(CGPoint)anchorpoint forView:(UIView *)view{
  CGRect oldFrame = view.frame;
  view.layer.anchorPoint = anchorpoint;
  view.frame = oldFrame;
}

总结

1、position是layer中的anchorPoint在superLayer中的位置坐标。
2、互不影响原则:单独修改position与anchorPoint中任何一个属性都不影响另一个属性。
3、frame、position与anchorPoint有以下关系:

1
2
frame.origin.x = position.x - anchorPoint.x * bounds.size.width;  
frame.origin.y = position.y - anchorPoint.y * bounds.size.height;

第2条的互不影响原则还可以这样理解:position与anchorPoint是处于不同坐标空间中的重合点,修改重合点在一个坐标空间的位置不影响该重合点在另一个坐标空间中的位置。

后记

20140323:关于修改anchorPoint为什么会移动layer的位置,在刚才回复finder的评论时想到了一个更好的解释:
还是以桌子与白纸为例,如果固定图钉在桌上的位置,也就是positon不变,这个时候图钉处在白纸的不同地方就是不同的anchorPoint,相应地也就是不同的frame。
另一方面,如果固定图钉在白纸上的位置(没订在桌子上),不管怎么平移白纸,anchorPoint肯定是不变的,但frame肯定是随之变化的

参考

一些MacOS技巧

作者 wonderffee
2013年10月7日 08:48

早早就醒了,天气又不好,无事可做,整理一下自己在使用Mac过程中用到的一些技巧吧,比较杂乱,以后不定期更新。

如何在命令行中使用Sublime Text

终端中输入下面的命令回车即可,之后就可以使用subl filename打开文件了。

1
sudo ln -s /Applications/Sublime\ Text\ 2.app/Contents/SharedSupport/bin/subl /usr/bin/subl

如何查看当前dns设置

cat /etc/resolv.conf

如何在命令行中快速修改DNS

1
sudo networksetup -setdnsservers Wi-Fi 199.91.73.222 178.79.131.110

如何在命令行中快速得到iOS设备的UDID

1
ioreg -w 0 -rc IOUSBDevice -k SupportsIPhoneOS | sed -n 's/.*USB Serial Number[^0-9a-z]*\([0-9a-z]*\).*/\1/p'

如何快速创建HTTP服务器用于文件共享

一行命令即可,在命令行输入“python -m SimpleHTTPServer 8888”,就可以创建以当前python命令运行的目录为根目录的HTTP服务器了。在浏览器打上http://xx.xx.xx.xx:8888/ 就能访问到python所运行的当前目录了,下载文件很方便。
——在微博上看到的这个技巧,高端大气上档次!

如何解决iTunes下载时出现”无法完成您的itunes store的请求,发生未知错误(-50)“问题

打开iTunes — 编辑 — 偏好设置 — 家长控制 — iTunes Store这一项勾选(把 允许访问iTunes U 这一项也勾选)— 确定。 这时iTunes Store会自动访问iTunes U,关闭iTunes。 重开iTunes,回到家长控制把之前勾选的两项取消,回iTunes Store重新登录,-50问题就解决了!

升级iOS7后利用rvictl和wireshark抓包失效?

作者 wonderffee
2013年10月6日 11:31

最近把一台设备升级到iOS7后,利用rvictl和wireshark抓包发现抓不了,无意中发现在装有xcode5的机器上可以抓包,看来rvictl与xcode是绑定的,升级到最新的iOS7后,必须要装上最新的xcode5版本才能抓包。

使用rvictl有一个前提是要获取设备的UDID,看网上不少教程都是从xcode中获取UDID,步骤相当繁琐,快速获取UDID用命令行才是王道,果然不出所料,很快就找到了三种命令行快速得到iOS设备的UDID方法,如下:

方法1:速度最快

1
ioreg -w 0 -rc IOUSBDevice -k SupportsIPhoneOS | sed -n 's/.*USB Serial Number[^0-9a-z]*\([0-9a-z]*\).*/\1/p'

方法2:不知道为什么不起作用了,因为iOS7的缘故?

1
system_profiler SPUSBDataType | sed -n -e '/iPhone/,/Serial/p' | grep "Serial Number:" | awk -F ": " '{print $2}'

方法3:

1
system_profiler SPUSBDataType | grep "Serial Number:.*" | sed s#".*Serial Number: "##

不过注意的是,用上面的命令得到的UDID并不一定是唯一的,比如我在MacBook Pro上就得不到唯一的UDID。

以下转载一下用rvictl和wireshark进行抓包的方法:

RVI(Remote Virtual Interface)是在iOS5中开始添加的,利用这个工具,在不需要开代理,也不需要越狱的情况下就可以抓到iOS设备上所有的包,所需要的一台装有Mac OS X的电脑以及USB数据线。我发现Android还没有类似的工具,iOS在这方面就方便多了。

基本的方法就是把设备通过USB连上mac上。然后为这台设备安装RVI,这个虚拟的在Mac上的网卡,就代表这台ios设备的使用网卡。然后在mac上跑抓包的工具,定位到这个虚拟的网卡上,来抓包。

(1)安装RVI,需要使用rvictl工具,以下步骤在mac的终端中操作:

1
2
3
4
5
6
7
8
9
10
11
12
$ # First get the current list of interfaces.
$ ifconfig -l
lo0 gif0 stf0 en0 en1 p2p0 fw0 ppp0 utun0
$ # Then run the tool with the UDID of the device.

$ rvictl -s 74bd53c647548234ddcef0ee3abee616005051ed
Starting device 74bd53c647548234ddcef0ee3abee616005051ed [SUCCEEDED]

$ # Get the list of interfaces again, and you can see the new virtual
$ # network interface, rvi0, added by the previous command.
$ ifconfig -l
lo0 gif0 stf0 en0 en1 p2p0 fw0 ppp0 utun0 rvi0

(2)安装成功后,此时其实可以用任何抓包工具来抓取。包括wireshark等。因为这时就会看到一个rvi0的网卡。不过今天我们介绍的是通过tcpdump来搞。

在终端中输入如下命令:

1
sudo tcpdump -i rvi0 -n -s 0 -w dump.pcap tcp

解释一下上面重要参数的含义:

-i rvi0 选择需要抓取的接口为rvi0(远程虚拟接口)
-s 0 抓取全部数据包
-w dump.pcap 设置保存的文件名称
tcp 只抓取tcp包

当tcpdump运行之后,你可以在iOS设备上开始浏览你想抓取的App,期间产生的数据包均会保存到dump.pcap文件中,当想结束抓取时直接终止tcpdump即可。然后在mac中找到dump.pcap文件。用wireshark打开就ok。

(3)去掉RVI这个虚拟网卡,使用下面的命令:

1
2
3

$ rvictl -x 74bd53c647548234ddcef0ee3abee616005051ed
Stopping device 74bd53c647548234ddcef0ee3abee616005051ed [SUCCEEDED]

整个流程就是这样的。自己动手操作一下吧。。

再详细的细节,请找一下苹果官方的资料:Technical Q&A QA1176。

参考链接:
Mac上命令行获取iPhone/iPad的Identifier(UUID) 的方法
一行命令 得到iOS设备的UDID
未越狱ios设备的抓包方法

❌
❌