通过自签名 SSL 证书分发安装 IPA
iOS 7.1 通过 itms-services://
安装 IPA 时要求 ipa.plist
必须 HTTPS 环境,不然会提示证书错误而无法安装。简单解决可以把 ipa.plist 放在 Dropbox 等支持 HTTPS 访问的地方,不过这样就不方便一键打包部署。其实可以通过自签名的 SSL 证书来解决这个问题。
1.创建自签名 CA 根证书,方便自动信任该 CA 所签发的证书:
openssl genrsa -out CA.key 2048
openssl req -x509 -new -key CA.key -out CA.cer -days 730 -subj /CN="Custom CA"
2.将 CA.cer
通过邮件等分发安装到设备作为信任证书。
3.创建 HTTPS URL 需要的密钥和证书:
openssl genrsa -out ipa.key 2048
openssl req -new -out ipa.req -key ipa.key -subj /CN=ipa.site.com
openssl x509 -req -in ipa.req -out ipa.cer -CAkey CA.key -CA CA.cer -days 365 -CAcreateserial -CAserial serial
4.上传 ipa.cer
和 ipa.key
到服务器,比如 /etc/nginx/ssl
目录下。
5.设置 Nginx 使用自签名证书:
server {
listen 443;
server_name ipa.site.com;
ssl on;
ssl_certificate /etc/nginx/ssl/ipa.cer;
ssl_certificate_key /etc/nginx/ssl/ipa.key;
location / {
root /home/fannheyward/ipas;
index index.html index.htm index.php;
}
gzip on;
}
6.注意修改脚本里 ipa.plist 地址和 ipa 地址为 HTTPS.
iOS 7 Background Fetch
iOS 7 新加了三个后台任务 API: Background Fetch
后台获取,Silent Remote Notifications
静默推送,Background Transfer Service
后台传输。
Background Fetch 会由系统进行调度,应用可以在后台进行一定的网络请求。这里的限制是后台操作只允许 30s,超时未完成应用会被直接 kill,所以只适合做一些简单的网络请求。
Silent Remote Notifications 可以由服务端控制,通过消息后台打开应用根据消息内容 (content-id) 进行一些操作,也可以做网络请求,但同样只有 30s 限制。
Background Transfer Services 可以在后台进行网络大文件的下载、上传操作,没有时间限制,但只能在 Wi-Fi 下进行,而且受系统调度可能会是间断性进行。一般可以配合静默推送一起用,比如电视剧更新,静默推送最新一集信息到手机,应用后台新建下载任务然后逐步下载,下载完成后再通过 Local Notifications 通知用户观看。
Background Fetch 使用步骤:
1 在 Target - Capabilities
打开 Background Modes
,勾选 Background Fetch
。也可以手动修改 Info.plist 添加 UIBackgroundModes - fetch
。
2 设置后台获取时间间隔:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
return YES;
}
3 执行后台获取,并在完成后通知系统:
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
//...
[fetcher fetchDataWithResult:^(NSError *error, NSData *data){
if (error) {
completionHandler(UIBackgroundFetchResultFailed);
} else {
// parse data
if (hasNewData) {
completionHandler(UIBackgroundFetchResultNewData);
} else {
completionHandler(UIBackgroundFetchResultNoData);
}
}
}];
}
需要注意的是一定要在请求完成后再调用 completionHandler();
,不然请求有可能被系统中断。可以配合 NSOperation + KVO 在所有操作都完成后再执行 completionHandler();
.
Xcode 5 提供了两个方法测试 Background Fetch,一是模拟器运行应用时通过 Xcode 菜单 Debug - Simulate Background Fetch
模拟;二是修改应用 Scheme 选中 Launch due to a background fetch event
再运行应用,这时候应用不会打开界面,真正的在后台运行。
参考 Multitasking in iOS 7, WWDC 2013 Session笔记 - iOS7中的多任务, iOS 7: Background Fetch.
NSOperation 笔记
iOS 下的多线程编程有 NSOperation 和 Grand Central Dispatch(GCD) 两种,简单记一些 NSOperation 的使用注意。
NSOperationQueue
相当于一个操作池,operation 添加进来后会按照 First-In-First-Out(FIFO) 的策略自动执行。operation 一般会添加到应用全局共享的自定义 queue,这样避免阻塞主线程的执行。
一些简单的多线程需求没必要动用 NSOperation 这个大家伙,NSInvocationOperation
就很方便:
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(anyWork)
object:nil];
[operationQueue addOperation:op];
- (void)anyWork
{
//perform any work in operation
}
NSInvocationOperation 不是很方便共享操作,如果某个操作会在很多地方需要,就可以做个 NSOperation 子类封装:
@implementation CustomOperation
- (void)main
{
//perform any work in operation
}
@end
这个子类只实现了 main
方法,相较 NSInvocationOperation 方便共享。如果需要对操作做更多细致化的功能,比如状态控制,就需要更加复杂的继承实现,参见 AFURLConnectionOperation,这种情况下不继承 main
,而是继承实现 start
cancel
等,然后通过 KVO 手动控制操作状态的切换。
NSOperation 可以设置依赖,A 操作依赖 B 操作完成后才能做,那么就可以设置 B 为 A 的依赖 [A addDependency:B];
.
如果各个操作之间没有依赖关系,但是又需要在全部操作都完成后做一些善后工作,有两个解决方案,一是添加所有操作为善后操作的依赖,这样所有其他操作完成后善后操作才会执行,这个方法较为死板,或者可以用 KVO 监听队列操作数,等操作都完成后队列操作为空的时候做善后工作:
[operationQueue addObserver:self forKeyPath:@"operationCount" options:0 context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (object == operationQueue && [keyPath isEqualToString:@"operationCount"]) {
if (operationQueue.operationCount == 0) {
// any final operation.
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
NSOperation or GCD
NSOperation 和 GCD 都能满足多线程需要,那么该选哪个?When to use NSOperation vs. GCD 的观点:
Always use the highest-level abstraction available to you, and drop down to lower-level abstractions when measurement shows that they are needed.
NSOperation 相比 GCD 提供了更多功能,比如操作执行状态,操作执行的暂停、取消,比如操作之间的依赖,比如控制操作队列同一时间可执行操作的数量。
GCD 相比 NSOperation 使用方便,系统开销小性能好。
实际项目中较为简单的小操作直接 GCD,灵活方便;规模较大控制复杂的操作还是通过 NSOperation 为好,也能享用高级 API 提供的方便。
Semantic Versioning 匹配
Semantic Versioning 直译为语义化版本,格式为 MAJOR.MINOR.PATCH
,比如 1.2.3
代表第一主版本第二次版本第三补丁修正版,版本号唯一且可比较,更多信息参考官网介绍。
在 Node.js package.json
或 Bower bower.json
就是用 semver 做版本检查。记个笔记,假设当前最新版是 1.2.5:
1.2.3
明确指定版本,需完全匹配,安装的版本就是1.2.3
.>1.2.3
大于指定版本,匹配符合条件的最新版1.2.5
.<1.2.3
小于指定版本,匹配符合条件的最新版1.2.2
.<=1.2.3
可以包括补丁修正测试版,比如1.2.3-beta
,1.2.3 - 2.3.4
等同于>=1.2.3 <=2.3.4
.~1.2.3
接近于 1.2.3 的版本,等同于>=1.2.3-0 <1.3.0-0
,也就是相同主版本,相同次版本,补丁修正大于等于所需版本均符合。这种情况是实际使用中最多的,保持最大的兼容性.~1.2
等同于>=1.2.0-0 <1.3.0-0
,相同主版本、相同次版本均符合,等于1.2.x
。~1
等同于>=1.0.0-0 <2.0.0-0
,相同主版本均可,等于1.x
.*
任意版本,会匹配当前可用的最新版1.2.5
.1.2.3 || 1.3.2
哪个满足取哪个,如果两者均符合取第二个。
主要就是 ~
匹配的使用,一般就用 ~1.2.3
,保持次版本内最新,又保持最大兼容性。
Docker 笔记
所有 docker 操作都需要 root 权限,需要加 sudo:
docker search gitlab
搜索服务镜像(image),挑选有 TRUSTED 标示的,相对好一些。
docker pull NAME
下载相应镜像,由于 index.docker.io 被墙,需要梯子,下载会很慢。
docker build -t NAME .
在当前目录根据 Dockerfile 构建容器,--rm
自动删除 build 中间状态的容器。
docker run -i -t -p 8080:80 NAME
运行一个 Container,支持的参数:
-d
Detached 或者 daemon mode,后台运行。-i -t
开一个 tty 终端,保持交互模式,这两个一般共同使用。-e
设置环境变量参数,参考 Install GitLab With Docker。-p [host_port]:[container_port]
映射 HOST 端口到容器,方便外部访问容器内服务,host_port 可以省略,省略表示把 container_port 映射到一个动态端口。-v [host-path]:[container-path]
把 HOST 文件夹挂载到 Container 用以保存数据。--rm
自动删除已运行存在的相同 IMAGE 的容器。
docker attach --sig-proxy=false CONTAINER
attach 后台运行的容器,加上 --sig-proxy=false
参数可以通过 Ctrl+C
detach,不然一旦 attach 就没办法取消。
docker commit --run='COMMAND' -m 'message' CONTAINER IMAGE:tag
登录容器做一些修改,退出到 HOST 保存修改到镜像,或者直接在 HOST 通过 --run
给正在运行的容器发送命令并保存到镜像。
docker stop/start/restart/kill/rm CONTAINER
停止、重启、杀死、删除容器。
docker ps -a -q
列出当前运行的容器,-a
会列出所有,包括已停止的,-q
只列出容器 ID。docker ps -a -q | xargs docker rm
可以删除所有未运行的容器。
docker logs -f CONTAINER
查看容器运行日志。
docker cp CONTAINER:/PATH HOSTPATH
拷贝容器内文件或文件夹到 HOST。目前只支持 Container 到 HOST 的单向拷贝,HOST 到 Container 可以通过 insert 命令。
docker insert IMAGE URL PATH
将 URL 文件内容写入相应 PATH,这个操作不修改原来 IMAGE 内容,而是再它的基础上新建一个 IMAGE.
docker images
列出已安装的镜像。可以通过 docker rmi IMAGE
删除镜像。
docker inspect CONTAINER | grep IPAddress
检查容器配置,包含内部 IP 等信息。
更多可参考 Docker 文档。
Install GitLab with Docker
GitLab 是个非常不错的 GitHub clone,很适合团队自建 git 服务器。但是由于 GitLab 是个 RoR 应用,加上 gitlab-shell 的权限要求等等,GitLab 的部署甚是麻烦。
Docker 简单说就是基于 LXC 的类 VM 方案,当然比 Virtual Box 等 VM 要高效、省资源,应用和依赖打包成一个容器,很方便部署。
用 Docker 部署 GitLab 首先要找一个可用的镜像(image):
sudo docker search gitlab
选用 sameersbn/gitlab,原因是更新较快,文档详细,支持 -e
设置环境变量,基本上不需要修改安装配置。
省事的话直接 sudo docker pull sameersbn/gitlab
即可,或者:
git clone https://github.com/sameersbn/docker-gitlab.git
cd docker-gitlab
添加 HOST /root/.ssh/id_rsa.pub 到 authorized_keys,这样可以免密码从 HOST 登录 Container。
sudo docker build -t fannheyward/gitlab .
build 完成后启动:
sudo docker run -p 22:22 -d \
-e "[email protected]" -e "SMTP_PASS=password" \
-e "[email protected]" -e "[email protected]" \
-e "GITLAB_SIGNUP=true" \
-e "GITLAB_HOST=gitlab.host.com" \
-v /opt/gitlab/data:/home/git/data \
-v /opt/gitlab/mysql:/var/lib/mysql \
fannheyward/gitlab
-e
用来设置一些环境变量,最少要把GITLAB_HOST
设置,不然所有项目的 git 地址为git@localhost
。-v [host-path]:[container-path]
用来把 HOST 文件夹挂载到 Container 用来保存数据,不然 Container 重启或者关停后数据就会丢失,前面是 HOST 目录,后面是 Container 目录,不要写反。-p 22:22
是把 Container 的 22 端口映射到 HOST 22 端口,HOST 22 改为其他,这样 git ssh 操作的时候方便一些。
在 HOST 上可以通过 ssh 172.17.0.2
登录 Container,IP 地址可以通过 docker inspect c8c2997b9bc4|grep IPAddress
获取。在 Container 里可以做任何修改,安装软件等,修改后在 HOST sudo docker commit c8c2997b9bc4 fannheyward/gitlab:v1
提交保存,这样重启 Container 不会丢失修改。
Done。数据备份和升级参考 sameersbn/gitlab 文档。
Make an iOS Static Library
做一个 iOS 静态库需要注意的东西:
-
namespace 冲突。静态库用了某第三方库,项目也用了同样的第三方库,在编译的时候就会有
duplicate symbol
错误,因为有两份同样的第三方库。解决办法就是把用到的第三方库加上自定义前缀,包括类名、delegate 协议、常量名,尤其需要注意 Category 的方法名要修改。 -
封装静态库的时候应尽量避免引入重量级第三方库,多自己进行封装。
-
一个静态库要有自己独有的前缀,所有类名、常量等都要加同样的前缀。
-
真机+模拟器支持。Xcode 默认只会用当前环境(真机或模拟器)生成静态库,这样的 SDK 不方便其他项目开发时调试。解决办法就是通过脚本生成一份通用库,build_universal_library.sh,via SO.
-
文档。静态库的方便是使用者直接拿你提供的方法来用,无需关注具体实现;不方便在于看不到实现,出现问题无法排查,因此需要把 SDK 的版本、更新历史、使用、FAQ 等写成文档,方便使用,也显得 SDK 比较正式规范。
-
图片等资源文件用 bundle 方式打包。一个简单制作 bundle 的方法:新建文件夹,重命名为
YourSDK.bundle
,然后Show Package Contents
打开,加入图片。使用图片的时候需要指明 bundle:[UIImage imageNamed:@"YourSDK.bundle/img.png"]
。也可以用 Target 方式制作 bundle,比如 iOS Library With Resources. -
修改 SDK product path,主要是方便打包,参见 build_universal_library.sh.
-
SDK 头文件加上版本号和简单的使用注释,开发者不太喜欢长篇大论的文档 :D.
-
如果 SDK 有用到 Category,注意项目设置
Other Linker Flags
添加-ObjC
,QA1490. -
开发时可以把 SDK 用子项目形式加到 SDKDemo 项目下,这样可以边开发边测试。SDKDemo 修改
User Header Search Paths
为${SYMROOT}/${CONFIGURATION}-universal/YourSDK
,路径和 build_universal_library.sh 保持一致。
Grunt serve with Proxy
在用 Grunt 开发时可能需要连接外部服务,比如 grunt serve
服务在 http://127.0.0.1:9000
, 当前页需要请求 http://127.0.0.1:9090/api
服务,这时候如果直接请求 /api
就变成了 http://127.0.0.1:9000/api
,结果 404,因为这个地址是不存在的;如果直接请求 http://127.0.0.1:9090/api
就会出现跨域问题。
比较方便的解决方案是在 grunt server 层做个代理,把 /api 请求转发到需要的服务。有个现成插件 grunt-connect-proxy 可以直接用。
下载安装:npm install grunt-connect-proxy --save-dev
,在 Gruntfile.js
添加 grunt.loadNpmTasks('grunt-connect-proxy');
启用。修改 connect
段设置,添加 proxies
和 livereload - middleware:
grunt.initConfig({
connect: {
options: {
port: 9000,
// Change this to '0.0.0.0' to access the server from outside.
hostname: 'localhost',
livereload: 35729
},
livereload: {
options: {
open: true,
base: [
'.tmp',
'<%= yeoman.app %>'
],
middleware: function (connect, options) {
var middlewares = [];
var directory = options.directory || options.base[options.base.length - 1];
if (!Array.isArray(options.base)) {
options.base = [options.base];
}
options.base.forEach(function(base) {
// Serve static files.
middlewares.push(connect.static(base));
});
// Setup the proxy
middlewares.push(require('grunt-connect-proxy/lib/utils').proxyRequest);
// Make directory browse-able.
middlewares.push(connect.directory(directory));
return middlewares;
}
}
},
proxies: [
{
context: '/api',
host: '127.0.0.1',
port: 9090,
https: false,
changeOrigin: false,
xforward: false
}
]
},
});
在 serve
任务下添加 configureProxies
,注意要加在 connect
任务前:
grunt.registerTask('serve', function (target) {
if (target === 'dist') {
return grunt.task.run(['build', 'connect:dist:keepalive']);
}
grunt.task.run([
'clean:server',
'bower-install',
'concurrent:server',
'autoprefixer',
'configureProxies',
'connect:livereload',
'watch'
]);
});
重启 grunt serve
即可。
Date Timestamp Conversion in Lua
Datetime to Timestamp
local dt = {year=2013, month=12, day=25, hour=0, min=0, sec=0}
print(os.time(dt))
-- 1387900800
Timestamp to Datetime
local ts = os.time()
print(os.date('%Y-%m-%d %H:%M:%S', ts))
-- 2013-12-25 22:09:51
More: Date and Time