准备把自己开发独立应用的文章整理成一个系列,本篇介绍下我最近刚上线的一个 app 《设计搞》的原理和开发中遇到的问题和心得等。
第一篇笔记见:开发笔记(一):个人开发者的困境与突破
简单介绍
app 下载地址:设计搞 on the App Store
主要是提供国外著名的设计网站 Dribbble 的图片和接口加速功能,Dribbble 虽然在国内没有被完全封禁,但是访问特别慢,尤其是网站中的图片,加载速度惨不忍睹。
因为我个人经常需要到 Dribbble 上寻找设计的灵感,特别是在移动设备上(iPad)需求非常强烈,所以一直以来,我都想做一个 client app,除了最基本的操作之外,最核心要提供 图片和接口加速。后来一想,这个事情好像很简单,于是说搞就搞,在网上找了一份开源的 client 代码,大改了一番,于是就成了“设计搞”这个名字很有意思的 app。
新版正在审核,logo 已经切换成了 banner 里的样子
核心功能
- 接口和图片加速,国内无障碍浏览,原理稍后介绍。
- 收藏,关注,喜欢等常规的官网提供的功能,完全与官网数据互通。
- 可以浏览并保存 GIF 到本地(iOS 11后的版本可保存 GIF),浏览 GIF 时候内存占用极小,回收及时。
- 随时分享设计稿到朋友圈,分享后会显示成一个 H5。
- 兼容 iPad,iOS 11,iPhone X,完全遵照原生风格设计。
核心技术
应用本身涉及到客户端技术、服务端技术以及爬虫技术。
客户端
1. 核心是 GIF 浏览与保存。在列表页的时候加载 GIF 用的是 SDWebImage 内置的 GIF 支持,这些 GIF 都是简化过的,所以内存占用不高。但是在详情页里,很多 GIF 帧数非常高,画幅很大,SDWebImage 内置的 GIF 支持是默认把所有帧都解析到内存的,一张 GIF 可能会有 几百兆 的内存占用,有的甚至超过 900 M,直接会导致应用崩溃。后来改用 SDWebImageDownloader 将 GIF 下载,然后用 YLGIFImage 来显示 GIF,YLGIFImage 是一帧一帧解析图片的,内存占用很少。最最关键的是在视图销毁的时候手动回收一下图片的引用(在 dealloc 中将 imageView 设为 nil)。
保存 GIF 是iOS 11 新增的功能,需要调用特殊的方法:
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
NSData *data = _shotData;
[library writeImageDataToSavedPhotosAlbum:data metadata:nil completionBlock:^(NSURL *assetURL, NSError *error) {
if (!error) {
[SVProgressHUD showSuccessWithStatus:@"保存成功!"];
}else{
[SVProgressHUD showErrorWithStatus:@"系统错误"];
}
}];
2. 其他都是普通的界面逻辑开发、推送集成、友盟集成、分享集成等,不过这里有一个问题,如果用原生的 UIActivityViewController 来实现分享的话,要注意在 iPad 上弹出分享视图时要兼容一下,否则会崩溃:
if ([activityVC respondsToSelector:@selector(popoverPresentationController)]) {
activityVC.popoverPresentationController.sourceView = self.view;
}
3. iOS 11 里的 safe area,如果你的应用有用伸缩布局之类的,要注意这个特性,iOS 11和 之前版本的 safe area 的定义不太一样,会导致一些界面的错位。解决方法也很粗暴,直接把 视图选项里的 safe area 取消勾选即可。
4. 另外,将你的 app 发布到 appstore 的时候,最近新增了一些政策,被我撞到的有:
- app 名字和一句话简介里不能出现其他知名 app 的名字。亲测,甚至连中英文,别名都不行,会被直接打回。具体“知名”怎么定义,不太清楚,反正 dribbble 这种名词肯定不行,导致我的 app 在搜索 dribbble 这个词的时候优先级很低。
- 应用的截图里不能出现任何苹果未发布的设备的照片或者设计,例如 iPhone X,一开始我截图里有一张设计稿是 iPhone X,直接被打回。另外小贴士一下,在 app 简介里,诸如 “iOS” 之类的苹果专用名词的大小写千万不要写错,会被打回。
- 应用的 icon,我的应用 icon 其实侵犯了 dribbble 的版权,最新版已经无法通过审核了,新的 logo 下一版就会换上,还没搞清楚苹果怎么和 dribbble 同步这个版权信息的。
服务端
1. 核心功能当然是如何用最底开发成本加入图片和接口访问。
我们先讲图片,其实很简单,可以利用很多云存储的镜像功能来简单的镜像图片,所谓的镜像,就是当你访问云存储的一个路径的时候,如果云存储上找不到对应文件,就会去镜像的站点拉取对应的访问路径的图片,存储并返回给客户端。这里,我们把 cdn.dribbble.com 作为镜像站点,我在取到所有图片之后,把 url 中的 cdn.dribbble.com 简单替换成云存储的域名即可。
例如把以下域名替换
变成:
yutou.oss-cn-hangzhou.aliyuncs.com/users/41818…
是同一个图片,并且路径完全一样。
2. 预镜像图片
上面已经实现了镜像功能,这时候我们把 app 里所有 cdn.dribbble.com 的图片访问域名替换掉即可完成加速。不过现在还有一个问题,第一次访问镜像图片的时候,因为需要去源站抓取原图,所以访问还是会慢。所以需要做个脚本,替换用户第一次去访问所有图片。
这个其实也很简单,写一个脚本,定时去请求 dribbble 列表页的接口,取出里面所有的图片,然后 wget 一遍,这时候云存储就会把对应的图片缓存下来。当然还有一些细节问题,例如因为 dribbble 的源站访问很慢,所以通常需要有重试机制之类的。
3. 接口加速。
高潮来了,图片加速其实很简单,但是因为用了 dribbble 的开放平台接口,接口访问仍然很慢,而且最致命的是,dribbble 对每个接口每分钟总的访问次数只有 60 次,崩溃啊,简直无法使用。必须要做接口加速+接口缓存了。
原理其实也很简单,在服务端用 Nodejs 做一个服务代理+强缓存。把客户端所有静态内容的请求,都转发到这个 Nodejs 服务,然后 Nodejs 服务把请求转发给 api.dribbble.com 把所有参数、header、路径 都转发给 dribbble。接收到结果之后,将内容按照path之类的特征缓存起来,下次接收到相同特征的请求直接返回缓存的内容,这样就可以达到接口加速的目的。
当然也有一些细节需要处理,例如缓存的特征到底是什么?还有 header 的透传,还有返回的 header 也需要缓存之类的。
技术方面的东西差不多就这些了,希望对大家有帮助。
关于推广,目前还没有正式推广过,就在知乎之类的平台发布过一些宣传,目前用户 3000 左右,还在不断增加中。感觉作为一个个人开发者,做个 app 要做宣传还是挺难的,毕竟没有钱很多渠道都上不了,不过我觉得可以争取被 apple 推荐,虽然这很难,但是只要不断优化使用体验和内容,总有一天会被推荐,近期也一直在迭代这个 app,修复了 N 个 bug 和体验问题了,已经发布了 六七个版本,相信不断的优化,会带来用户的支持。另外后续也会有一些内容方面的计划,对应用的推广和粘度应该会有不错的帮助,需要慢慢去实现。