iOS Crash Report Service Comparison
实验对比一下现有的 iOS Crash report 服务。包括 Google Analytics(GA), Crashlytics, TestFlight, HockeyApp/QuincyKit/HockeyKit, Crittercism, Bugsense, Flurry.
Google Analytics
- 手动或 CocoaPods 添加库,设置统计 ID,开启 trackUncaughtException,使用很简单。
- Crash 报表比较简陋,可以根据应用版本号、iOS 版本区分,然后根据 crash description 分类,堆栈描述信息比较少,只有 crash 部分栈信息。
- 通过 try-catch 可以有目的性的对 NSException、NSerror 进行捕捉。
- GA 2.0 仍在 beta,稳定性需要验证 。
- 免费。
GA 集成,可以少添加一个库,类似统计的方式做 crash report,crash 信息比较简单,适合简单使用。
Crashlytics
- 相较其他库手动添加或者用 CocoaPods 方式引入,Crashlytics 需要一个软件来集成,刚开始会比较不习惯。按流程走,选中项目,添加 Build Phase-Run Script,添加 framework,设置 APIKey,Done。
- 堆栈信息完善,crash 自动分类,然后作为一个 issue 列出,可以列出 crash 设备信息 (JailBroken, free space, free RAM,屏幕旋转方位,network type 等),这些信息对于 crash 筛选和原因查找会有很大帮助。
- issue 有 open/close 两个状态,方便解决统计。
- 支持 developer team。
- 被动收集,没有主动收集方式。
- crash 邮件报告,支持 Redmine 等第三方服务集成,方便 bug 提交管理。
- 被 Twitter 收购后企业版改为免费。
Crash 信息完善,分类清晰,适合对 crash report 要求比较高的场景使用。
TestFlight
- 手动或 pod 添加,打包上传到 TestFlight,获取 token。使用逻辑比较混乱,先上传 app 才能拿到 token。
- 支持应用分发,feedback,remote logs,Sessions,Checkpoint 等统计功能。
- 单纯 crash 的话使用还比较简单,不需要做特殊处理,其他功能需要针对处理。
- crash 发送好像有点问题,crash 了几次后服务器都没有收到,所以也没法看到 crash 统计。
- 支持 developer team。
- 免费。
看起来功能很多,但是都不够深度,crash report 功能不堪大用。
HockeyApp/QuincyKit/HockeyKit
- pod 添加 SDK,打包上传,获取 token;手动添加流程看起来非常麻烦。
- crash 自动分类,栈信息完整,会把关键信息提炼出来。crash status-Open/Resolved/Ignored。
- 支持应用分发、feedback。
- 支持和第三方 bug tracker 集成。
- HockeyKit、QuincyKit 是开源版本的 HockeyApp,均有客户端和服务端代码,QuincyKit 只有 crash report,HockeyKit 只有应用分发和更新。
- 手动上传 dSYM。
- HockeyApp 收费,免费试用一个月。
简单说就是 TestFlight 加强版,应用分发 + crash report.
Crittercism
- 使用简单,先在网站注册一个应用,获取 token,不需要上传 ipa 到网站。
- 按 crash 原因归类,堆栈信息完整,高亮标明主要信息。crash 报表清晰,可以很明确的查看 crash 历史,设备信息(RAM,iOS version,device,network 等)。
- 支持主动有目的性 exception 收集。
- 支持 crash status(unresolved,resolved,known)。
- 需要手动上传 dSYM,估计是为了 release 下使用。
- 支持 crash alarm,SMS、邮件接收,支持 Uservoice 服务集成。
- 支持 developer team。
- 居然还有一个 rate app alert 功能。。。
- 有免费套餐,专业版支持简单的应用统计。专业版每月活跃用户 100K (per 100k MAU),限制比较大。
- 初创公司,获得风投,和 Crashlytics 气质最像的一个。
功能强大的专业的 crash report 服务。
Bugsense
- 网站注册应用,获取 token。
- 客户端是用 PlCrashReporter 做 crash 收集。
- crash report 可以按 status/App version/OS version 过滤 (付费版)。
- 发送 crash report 的时候可以附带一些自定义数据。
- 有一个比较神奇的功能,Fix Notification,如果某个 crash 已经标记为 resolved 并且新版本已经上线,可以弹窗提醒用户该 crash 已经解决,引导用户去更新升级 (付费版)。
- crash 收集服务被墙。 There are cases where our servers are being blocked due to geographic restrictions (e.g. China).
- 应用应用使用统计,支持 Event 统计
- 免费版限制太多,基本不可用。
相对来说功能比较简单的 crash report 服务。
Flurry
- 网站注册应用,获取 application key。
- 做统计出身,所以 crash report 功能只能算是一个附属功能,crash log 非常简单。
- 免费。
统计服务附带 crash report,功能简单。
小结
- crash report 要求不高且在用 GA/Flurry 统计的话,直接用附带的。
- 需要更为专业详细的 crash report,Crashlytics/Crittercism 二选一。
- 需要应用分发的话上 HockeyApp。
个人倾向于 Crashlytics。原因:
- 内部测试应用分发都比较简单,可以用脚本+内部服务器搞定,比如这个 build.py。
- Crittercism 有 MAU 限制,付费升级到 Premium 也限制 100K MAU。
- Twitter 收了 Crashlytics 后很大方的把企业版免费,开发也在继续。
- Crashlytics 的网站设计更喜欢一些。
NSDateFormatter 返回一年前时间
NSDateFormatter
的一个小陷阱:
NSString *ds = @"2013-03-01 23:55:56";
NSDateFormatter* formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"YYYY-MM-dd HH:mm:ss"];
NSDate *date1 = [formatter dateFromString:ds];
NSLog(@"date1: %@", date1); //date1: 2012-03-01 15:55:56 +0000
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
NSDate *date2 = [formatter dateFromString:ds];
NSLog(@"date2: %@", date2); //date2: 2013-03-01 15:55:56 +0000
格式化以后是一年前的一个时间点,yyyy
指代的就是常规意义上的年,而 YYYY
是 Week of Year, 具体解释参考 Wikipedia: ISO_week_date
iOS 开发文档有相关提示:
It uses yyyy to specify the year component. A common mistake is to use YYYY. yyyy specifies the calendar year whereas YYYY specifies the year (of “Week of Year”), used in the ISO year-week calendar. In most cases, yyyy and YYYY yield the same number, however they may be different. Typically you should use the calendar year.
NSMutableAttributedString Notes
Core Text 针对文本段落支持的样式属性:
typedef CF_ENUM(uint32_t, CTParagraphStyleSpecifier) {
kCTParagraphStyleSpecifierAlignment = 0,
kCTParagraphStyleSpecifierFirstLineHeadIndent = 1,
kCTParagraphStyleSpecifierHeadIndent = 2,
kCTParagraphStyleSpecifierTailIndent = 3,
kCTParagraphStyleSpecifierTabStops = 4,
kCTParagraphStyleSpecifierDefaultTabInterval = 5,
kCTParagraphStyleSpecifierLineBreakMode = 6,
kCTParagraphStyleSpecifierLineHeightMultiple = 7,
kCTParagraphStyleSpecifierMaximumLineHeight = 8,
kCTParagraphStyleSpecifierMinimumLineHeight = 9,
kCTParagraphStyleSpecifierLineSpacing = 10, /* deprecated */
kCTParagraphStyleSpecifierParagraphSpacing = 11,
kCTParagraphStyleSpecifierParagraphSpacingBefore = 12,
kCTParagraphStyleSpecifierBaseWritingDirection = 13,
kCTParagraphStyleSpecifierMaximumLineSpacing = 14,
kCTParagraphStyleSpecifierMinimumLineSpacing = 15,
kCTParagraphStyleSpecifierLineSpacingAdjustment = 16,
kCTParagraphStyleSpecifierLineBoundsOptions = 17,
kCTParagraphStyleSpecifierCount
};
使用方法:新建一个样式 CTParagraphStyleSetting
,设置样式属性和相关值,然后添加到 NSAttributedString.
NSMutableAttributedString *attriStr = [[NSMutableAttributedString alloc] initWithString:string];
// 样式1: 两端对齐
CTTextAlignment alignment = kCTJustifiedTextAlignment;
CTParagraphStyleSetting alignmentStyle;
alignmentStyle.spec = kCTParagraphStyleSpecifierAlignment;//对齐属性
alignmentStyle.valueSize = sizeof(alignment);
alignmentStyle.value = &alignment;
// 样式2:行间距
CGFloat lineSpaceMax = 4.0f;
CTParagraphStyleSetting lineSpaceStyleMax;
lineSpaceStyleMax.spec = kCTParagraphStyleSpecifierMaximumLineSpacing;//最大行间距属性
lineSpaceStyleMax.valueSize = sizeof(lineSpaceMax);
lineSpaceStyleMax.value = &lineSpaceMax;
CGFloat lineSpaceMin = 1.0f;
CTParagraphStyleSetting lineSpaceStyleMin;
lineSpaceStyleMin.spec = kCTParagraphStyleSpecifierMinimumLineSpacing;//最小行间距属性
lineSpaceStyleMin.valueSize = sizeof(lineSpaceMin);
lineSpaceStyleMin.value = &lineSpaceMin;
CGFloat lineSpaceAdjust = 2.0f;
CTParagraphStyleSetting lineSpaceStyleAdjust;
lineSpaceStyleAdjust.spec = kCTParagraphStyleSpecifierLineSpacingAdjustment;
lineSpaceStyleAdjust.valueSize = sizeof(lineSpaceAdjust);
lineSpaceStyleAdjust.value = &lineSpaceAdjust;
// 样式3:最大行高
CGFloat lineHeightMax = 18.0f;
CTParagraphStyleSetting lineHeightMaxStyle;
lineHeightMaxStyle.spec = kCTParagraphStyleSpecifierMaximumLineHeight;//最大行高属性
lineHeightMaxStyle.valueSize = sizeof(lineHeightMax);
lineHeightMaxStyle.value = &lineHeightMax;
// 样式数组
CTParagraphStyleSetting settings[]={
alignmentStyle, lineSpaceStyleMax, lineSpaceStyleMin, lineSpaceStyleAdjust, lineHeightMaxStyle
};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(settings, 5);
[attriStr addAttribute:(id)kCTParagraphStyleAttributeName
value:(__bridge id)paragraphStyle
range:NSMakeRange(0, [attriStr length])];
CFRelease(paragraphStyle);
// Emoji、中文、英文混排
NSDictionary *fontAttributes = @{
(id)kCTFontFamilyNameAttribute : @"Helvetica",
(id)kCTFontCascadeListAttribute : @[
(__bridge id)CTFontDescriptorCreateWithNameAndSize(CFSTR("AppleColorEmoji"), 0),
(__bridge id)CTFontDescriptorCreateWithNameAndSize(CFSTR("ZapfDingbatsITC"), 0),
]
};
CTFontDescriptorRef descriptor = CTFontDescriptorCreateWithAttributes((__bridge CFDictionaryRef)(fontAttributes));
CTFontRef font = CTFontCreateWithFontDescriptor(descriptor, FONT_SIZE, 0);
[attriStr addAttribute:(id)kCTFontAttributeName
value:(__bridge id)font
range:NSMakeRange(0, [attriStr length])];
CFRelease(font);
// 字体颜色
[attriStr addAttribute:(id)kCTForegroundColorAttributeName
value:(id)FONT_COLOR.CGColor
range:NSMakeRange(0, [attriStr length])];
OS X 下用 IPFW 作端口转发
用 ipfw 监听本地 80 端口然后转发到 8080 等端口,方便本地开发时调试操作。ipfw 是 OS X 自带的防火墙程序,类似 Linux 下的 iptables。
- 查看当前 ipfw 规则:
sudo ipfw show
- port 80 to 8080 forward:
sudo ipfw add 100 fwd 127.0.0.1,8080 tcp from any to any 80 in
- 清除 ipfw 规则
sudo ipfw flush
Done.
via Port Forwarding (80 to 8080 for Tomcat) Using IPFW on Mac OSX
[self review:2012];
2012
2012 年度个人总结。先对一下去年计划:
工作
加强 iOS 开发,尤其是新技术的学习和整理,比如 ARC。
Done.
尝试 iPad 开发,这可不是简单的界面放大,整个用户交互都需要重新学习理解。
Done.
服务端开发,至少是应用级别要跟上,已经落户一步,不能拖太久。
FAIL
继续加强产品能力,尤其是整体把握。
Fail.
生活
完婚。
DONE.
给家里更多的帮助,让爸妈轻松一点。
Done.
希望今年能有一次旅游,厦门?
Done. 上半年的张家界+年底的版纳旅游。
每项按十五分的话刚刚过 70,及格分。
工作
今年在工作上做了一些侧重,尤其是下半年,更多偏向于技术开发。自己在产品、交互等方面并不特别擅长,可能偶尔会有一些灵感飞过,但整体上的把控力还很欠缺,细节不够严谨。所以从下半年开始,更多的重心放在技术上,code review,产品代码质量把控上。我不知道这种侧重好还是不好,对自己以后的职业走向是利还是弊,暂时来说我想先把技术能力再提高一下。团队也有很多参与产品的机会,所以产品方面还可以继续锻炼。
侧重技术并不是自己技术有多牛,相反,越做越发现自己还有太多太多不懂的东西。有不懂才会有收获,今年工作上最多的收获来自 iOS 开发有了一些新的技术学习和积累。iOS 开发是一个进化非常快的领域,保持积极的学习状态很重要。ARC、GCD、Blocks、Core Data、AFNetworking、CocoaPods、UITableView 以及 App 的性能优化等等,这些技术点都遇到了很多坑,交了学费,但是对自己的技术成长很有好处。问题的解决和技术学习都留了一些笔记,好记性不如烂笔头。
相较于客户端开发有了一些积累,服务端开发今年没有任何进步,这一点很失望。主要原因是自己太懒散,缺乏动手去做,没有实践就不能发现问题,没有问题连学费都没得交,怎么可能有进步。所以新的一年里服务端开发是自己要着重加强的,不管什么一定要动手去做。
生活
我们结婚了,这就是今年的最大的成就。罗列好需要做的工作,然后两个人一起努力各个击破,这种感觉很不错,不啃老,我们做到了。爸妈牵挂我们在外的漂泊,我们更心疼他们在家的辛苦。让爸妈的压力不那么大是我们最想要的,为此我们会尽自己最大的努力去分担。
我还在坚持着,努力着,在外漂泊的不安定感却一直都在。这种感觉从 12 岁出去念初中开始,只有宿舍没有家。还好,我们现在两个人在一起,我不坚强的时候有你,你不坚强的时候有我,该哭的时候哭了,该笑的时候笑了。
2013
工作
- iDev 深入,比如 runtime,自动化测试等,尝试一下 OS X 开发。
- 服务端开发学习。
- 学一门新语言,Lua/Go。
生活
- 学车考驾照。
- 健身锻炼。
Hidden Features in osx.plugin.zsh
oh-my-zsh 自带了很多插件,其中 osx.plugin.zsh 里有不少好东西。
cdf
快速在终端打开当前 Finder 所在目录。之前的方法是用 cdto,缺点是会另外开一个终端窗口;或者鼠标拖动目录到终端再 cd。cdf 就省力很多,也是这个插件最喜欢的一个命令。trash
替换 rm,文件被移动到废纸篓而不是真正删除,避免误操作。alias rm='trash'
pfd/pfs
打印当前 Finder 所在目录,一般配合 cdf 来用。pushdf
pushd 寄存当前 Finder 所在目录。quick-look
调用 QuickLook 查看文件,配合 QLMarkdown.qlgenerator 快速预览 Markdown 文件。man-preview
把 man 信息生成 pdf 然后用预览打开。tab/split_tab/vsplit_tab
新建、切割终端 tab,不太习惯切割终端,这个用的很少。
Setup Octopress from existing repo
从已有的 Octopress repository 重新配置 GitHub Pages 托管博客,比如换了电脑却没有备份原来的设置。要求 source 分支已 push。
git clone [email protected]:fannheyward/fannheyward.github.com.git blog
cd blog
git checkout --track origin/source
# setup ruby with rbevn or rvm
gem install bundler
bundle install
rake gen_deploy # in order to create _deploy dir
# setup blog branch
cd _deploy/
git init
git add .
git commit -m "new setup."
git remote add origin [email protected]:fannheyward/fannheyward.github.com.git
cd ..
rake deploy
其实就是做了一系列的 git 操作,设置 repo,branch 等,熟悉 git 很容易搞定。
New Mac Setup
换了 SSD,重新配置了开发环境,简单留个笔记。
- Mac App Store 下载 Xcode,安装 Command Line Tools 方便编译。MAS 下载的一个好处就是后续可以增量更新。
- 安装 Homebrew,通过 brew 安装管理其他工具 git,zsh,MacVim,redis,PostgreSQL 等。
- 配置 oh-my-zsh,懒人必备。
- clone back dotfils from GitHub. 每个人都应该在 GitHub 等托管一份自己的配置文件,DRY。
- brew install rbenv ruby-build,主要是给 Octopress 用,之前用 rvm 过于庞大复杂,rbenv 就简单不少。
- 配置 Python virtualenv 环境,easy_install pip and use pip to install others.
换硬盘就显示了云存储的方便。Firefox Sync 很快就是自己顺手的浏览器,Dropbox 同步 nvALT 笔记,Alfred 等软件配置,只需一个账号你的数据、习惯随手就来,这也就是 Chromoe OS 带来的未来。
TP-Link WR941N 刷 OpenWrt
硬件版本 TP-Link WR941N V4/V5 00000000,软件版本 3.11.7 build 100723,从 OpenWrt trunk 下载对应固件 openwrt-ar71xx-generic-tl-wr941nd-v4-squashfs-factory.bin。其他型号参考 Table of Hardware 下载固件。
登录路由器升级固件,待路由自动重启后 telnet 192.168.1.1
连上路由器,passwd
设置密码,之后就可以通过 ssh [email protected]
登录路由器。
OpenWrt 默认没有开启无线网络,参考 TP-Link TL-WR941ND 手动修改 vi /etc/config/wireless
: (修改之前最好备份一下配置文件)
config wifi-device radio0
option type mac80211
option channel 11
option hwmode 11ng
option path 'pci0000:00/0000:00:00.0'
option htmode HT20
list ht_capab SHORT-GI-40
list ht_capab TX-STBC
list ht_capab RX-STBC1
list ht_capab DSSS_CCK-40
# REMOVE THIS LINE TO ENABLE WIFI:
# option disabled 1 (删除或注释这一行)
config wifi-iface
option device radio0
option network lan
option mode ap
option ssid OpenWrt
option encryption psk (默认没有加密,修改为 psk)
option key xxxxxxxxx
重启路由网络:
/etc/init.d/network restart
配置 PPPoE,vi /etc/config/network
:
config interface 'wan'
option ifname 'eth1'
option proto pppoe
option username 1234567
option password 7654321
option macaddr xx:xx:xx:xx:xx:xx (克隆路由器网卡地址)
再次重启路由网络进行拨号。
安装 LuCI web 界面,这样就可以在浏览器操作路由:
opkg update
opkg install luci
/etc/init.d/uhttpd enable
/etc/init.d/uhttpd start
现在就可以通过 http://192.168.1.1
修改路由器配置。
如果网络修改失败不能 ssh 登录,可以进入安全模式恢复:
路由上电时,灯会全亮一下,这时你要全神贯注了,当sys灯再次亮时,要立刻按reset2-3秒,然后你就会发现sys灯快闪了。这说明,安全模式成功了!
本机 IP 改为 192.168.1.5,telnet 192.168.1.1
连上,然后 firstboot
恢复。